ReactiveCocoa-RACChannel

之前分析过 RACSignal,数据流向是单向而且是1对1的,如果想使用双向绑定的效果,可以使用 ReactiveCocoa 框架中提供的 RACChannel;接来下分析 RACChannel 底层的实现原理。

我们以一个例子开始分析,比如将视图控制器中的 UITextField text 属性和 viewModel 中的 inputText 属性进行双向绑定,一般会这么做:

1
RACChannelTo(viewModel, inputText) = textField.rac_newTextChannel;

绑定之后,当 textField 的输入内容变化之后,会通知到 viewModel 的 inputText 进行变化,同样的当

inputText 被主动修改之后 textField 的输入框文本也会同步修改

image-20190401151228767

RACChannel

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface RACChannel<ValueType> : NSObject

@property (nonatomic, strong, readonly) RACChannelTerminal<ValueType> *leadingTerminal;

@property (nonatomic, strong, readonly) RACChannelTerminal<ValueType> *followingTerminal;

@end

@interface RACChannelTerminal<ValueType> : RACSignal<ValueType> <RACSubscriber>

- (instancetype)init __attribute__((unavailable("Instantiate a RACChannel instead")));

- (void)sendNext:(nullable ValueType)value;

RACChannel 持有2个 RACChannelTerminal 类型的只读属性 leadingTerminalfollowingTerminal,RACChannelTerminal 是 RACChannel 的子类并实现了 RACSubscriber 协议

对 viewModel 的数据进行的操作会发送给 leadingTerminal 再通过内部转发给 followingTerminal,由于 textField 是 followingTerminal 的订阅者,所以消息最终会发送到视图上

类似的当 textField 输入内容发生变化的时候会把数据发送给 followingTerminal,然后内部转发给 leadingTerminal,因为 viewModel 是 leadingTerminal 的订阅者,所以数据最终会通知给 viewModel。

RACChannelTerminal

在这个过程中,RACChannelTerminal 作用类似管道(pipe)和网络连接中的 socket,都表示连接中的一端;RACChannelTerminal 执行 sendNext,类似 socket 进行 write 给对端发送数据,类似的 subscribe 可以类比成 socket read。

了解了大概作用之后,在看看 RACChannelTerminal 的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@interface RACChannelTerminal<ValueType> ()

/// The values for this terminal.
@property (nonatomic, strong, readonly) RACSignal<ValueType> *values;

/// A subscriber will will send values to the other terminal.
@property (nonatomic, strong, readonly) id<RACSubscriber> otherTerminal;

- (instancetype)initWithValues:(RACSignal<ValueType> *)values otherTerminal:(id<RACSubscriber>)otherTerminal;

@end

@implementation RACChannelTerminal

#pragma mark Lifecycle

- (instancetype)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal {
NSCParameterAssert(values != nil);
NSCParameterAssert(otherTerminal != nil);

self = [super init];

_values = values;
_otherTerminal = otherTerminal;

return self;
}

#pragma mark RACSignal

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
return [self.values subscribe:subscriber];
}

#pragma mark <RACSubscriber>

- (void)sendNext:(id)value {
[self.otherTerminal sendNext:value];
}

- (void)sendError:(NSError *)error {
[self.otherTerminal sendError:error];
}

- (void)sendCompleted {
[self.otherTerminal sendCompleted];
}

- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
[self.otherTerminal didSubscribeWithDisposable:disposable];
}

@end

RACChannelTerminal 定义了2个私有属性:

  • values:RACSignal,是所发送数据的信号源
  • otherTerminal:RACChannelTerminal类型,接受 values 发出信号的对端端点

RACChannelTerminal 被订阅,比如订阅者调用 -subscribeNext: 等方法的时候,会执行 -subscribe 方法,于是最终订阅者会订阅 values 信号;当 RACChannelTerminal 发送信号的时候则会转发给对端断点 otherTerminal。

RACChannel 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (instancetype)init {
self = [super init];

// We don't want any starting value from the leadingSubject, but we do want
// error and completion to be replayed.
RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"];
RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"];

// Propagate errors and completion to everything.
[[leadingSubject ignoreValues] subscribe:followingSubject];
[[followingSubject ignoreValues] subscribe:leadingSubject];

_leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"];
_followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"];

return self;
}

@end

初始化过程中首先会先初始化2个 RACReplaySubject 变量:

  1. leadingSubject:capacity为0,订阅者只能收到订阅之后 leadingSubject 发送的信号

  2. followingSubject:capacity为1,订阅者订阅之后马上会搜到 followingSubject 之前最新发送的一个信号

  3. 将 followingSubject 设置成 leadingSubject 的订阅者,只接受 followingSubject 发送的 sendCompleted / sendError;同理 leadingSubject 也是 followingSubject 的订阅者并且只会收到 sendCompleted / sendError 事件。

    处理后,当 leadingSubject(followingSubject) 发送 sendCompleted / sendError 之后,followingSubject(leadingSubject) 的 订阅者会收到相关信号。

  4. 初始化 leadingTerminal 和 followingTerminal,leadingTerminal/followingTerminal 被订阅的时候,实际上是订阅私有属性 values 的信号,leadingTerminal/followingTerminal 执行 sendNext 发送信号的时候会,其订阅者不会收到信号,而是转发给私有属性 otherTerminal 发送给它的订阅者

RACChannel 双向传输过程

分析完 RACChannel 初始化过程之后,回到文章开头的例子

1
RACChannelTo(viewModel, inputText) = textField.rac_newTextChannel;

首先看宏 RACChannelTo

1
2
3
4
5
6
7
#define RACChannelTo(TARGET, ...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(RACChannelTo_(TARGET, __VA_ARGS__, nil)) \
(RACChannelTo_(TARGET, __VA_ARGS__))

#define RACChannelTo_(TARGET, KEYPATH, NILVALUE) \
[[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)]

内部是调用了 RACKVOChannel 的方法 -initWithTarget:keyPath:nilValue:

1
[[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (instancetype)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue {
NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0);

NSObject *strongTarget = target;

self = [super init];

_target = target;
_keyPath = [keyPath copy];

......

if (strongTarget == nil) {
[self.leadingTerminal sendCompleted];
return self;
}

RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) {
[self destroyCurrentThreadData];
return;
}

[self.leadingTerminal sendNext:value];
}];

......

[[self.leadingTerminal
finally:^{
[observationDisposable dispose];
}]
subscribeNext:^(id x) {
NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target);
if (object == nil) return;

......

[object setValue:x ?: nilValue forKey:lastKeyPathComponent];
} error:^(NSError *error) {
NSCAssert(NO, @"Received error in %@: %@", self, error);
NSLog(@"Received error in %@: %@", self, error);
}];

@weakify(self);

[strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
@strongify(self);
[self.leadingTerminal sendCompleted];
self.target = nil;
}]];

return self;
}

省略部分代码,其主要流程:

  1. 首先判断 taget 是否为空,如果是 nil 则 leadingTerminal 发送 sendCompleted,followingTerminal 的订阅者此时会收到 sendCompleted 事件。反之则执行步骤2
  2. 调用 KVO 监听到 target 对应 keyPath 变化的时候,leadingTerminal 发送 value,followingTerminal 订阅者会收到该 value
  3. 订阅 leadingTerminal 收到 value 之后利用 KVC 赋值到 target 对应的属性

image-20190402215557859

初始化完 RACKVOChannel 之后,RACChannelTo 宏后半部分 [@keypath(RACKVOChannel.new, followingTerminal)] 直接上会调用其重载的函数 -objectForKeyedSubscript: 并返回 followingTerminal 对象

1
2
3
4
5
6
7
8
- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key {
NSCParameterAssert(key != nil);

RACChannelTerminal *terminal = [self valueForKey:key];
NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key);

return terminal;
}

样例代码可以改变成

1
[[RACKVOChannel alloc] initWithTarget:viewModel keyPath:@"inputText" nilValue:nil][@"followingTerminal"] = [[RACKVOChannel alloc] initWithTarget:textField keyPath:@"text" nilValue:nil][@"followingTerminal"];

这里进行 = 号进行复制,最终会执行重载方法 - (void)setObject:forKeyedSubscript:

1
2
3
4
5
6
7
- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key {
NSCParameterAssert(otherTerminal != nil);

RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key];
[otherTerminal subscribe:selfTerminal];
[[selfTerminal skip:1] subscribe:otherTerminal];
}

根据样例代码分析,这里 selfTerminal 是 viewModel 侧的 followingTerminal,otherTerminal 则是 textField 侧的 followingTerminal;

otherTerminal 把订阅者设置为 selfTerminal,otherTerminal 执行 sendNext 的时候,selfTerminal 的订阅者会收到信号;

同理 selfTerminal 把订阅者设置为 otherTerminal,selfTerminal 执行 sendNext 的时候,otherTerminal 的订阅者会收到信号,但是只会收到 selfTerminal 第二个信号及其后续信号。

image-20190403111110637

至此,RACChannel 双向通信大概过程分析已经完成

备注:

- (void)setObject:forKeyedSubscript:-objectForKeyedSubscript: 相关资料可参考: 对象下标索引