之前分析过 RACSignal,数据流向是单向而且是1对1的,如果想使用双向绑定的效果,可以使用 ReactiveCocoa 框架中提供的 RACChannel;接来下分析 RACChannel 底层的实现原理。
我们以一个例子开始分析,比如将视图控制器中的 UITextField text 属性和 viewModel 中的 inputText 属性进行双向绑定,一般会这么做:
1 RACChannelTo(viewModel, inputText) = textField.rac_newTextChannel;
绑定之后,当 textField 的输入内容变化之后,会通知到 viewModel 的 inputText 进行变化,同样的当
inputText 被主动修改之后 textField 的输入框文本也会同步修改
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 类型的只读属性 leadingTerminal
、followingTerminal
,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 > ()@property (nonatomic , strong , readonly ) RACSignal<ValueType> *values;@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]; RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0 ] setNameWithFormat:@"leadingSubject" ]; RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1 ] setNameWithFormat:@"followingSubject" ]; [[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 变量:
leadingSubject:capacity为0,订阅者只能收到订阅之后 leadingSubject 发送的信号
followingSubject:capacity为1,订阅者订阅之后马上会搜到 followingSubject 之前最新发送的一个信号
将 followingSubject 设置成 leadingSubject 的订阅者,只接受 followingSubject 发送的 sendCompleted / sendError;同理 leadingSubject 也是 followingSubject 的订阅者并且只会收到 sendCompleted / sendError 事件。
处理后,当 leadingSubject(followingSubject) 发送 sendCompleted / sendError 之后,followingSubject(leadingSubject) 的 订阅者会收到相关信号。
初始化 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 ; }
省略部分代码,其主要流程:
首先判断 taget 是否为空,如果是 nil 则 leadingTerminal 发送 sendCompleted,followingTerminal 的订阅者此时会收到 sendCompleted 事件。反之则执行步骤2
调用 KVO 监听到 target 对应 keyPath 变化的时候,leadingTerminal 发送 value,followingTerminal 订阅者会收到该 value
订阅 leadingTerminal 收到 value 之后利用 KVC 赋值到 target 对应的属性
初始化完 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 第二个信号及其后续信号。
至此,RACChannel 双向通信大概过程分析已经完成
备注:
- (void)setObject:forKeyedSubscript:
和 -objectForKeyedSubscript:
相关资料可参考: 对象下标索引