簡(jiǎn)介
在 ReactiveObjC 中,根據(jù)數(shù)據(jù)流的方向,我們可以劃分出兩種不同數(shù)據(jù)流,即:?jiǎn)蜗驍?shù)據(jù)流,如:RACSignal、RACSubject、RACMulticastConnection;雙向數(shù)據(jù)流,如:RACChannel、RACKVOChannel。這篇文章主要介紹 ReactiveObjC 中的雙向數(shù)據(jù)流。當(dāng)我們需要實(shí)現(xiàn)數(shù)據(jù)的雙向綁定時(shí)(A的改動(dòng)影響B(tài),B的改動(dòng)也影響A),使用 ReactiveObjC 提供的雙向數(shù)據(jù)流可以很方便的實(shí)現(xiàn)相關(guān)需求。
RACChannel
RACChannel 類似一個(gè)雙向連接,連接的兩端都是 RACSignal 實(shí)例。RACChannel 像一個(gè)魔法盒子,我們可以在A端發(fā)送信號(hào),在B端訂閱A端的信號(hào),也可以在B端發(fā)送信號(hào),在A端訂閱B端的信號(hào)。如下所示:

1、RACChannelTerminal
在看 RACChannel 的源碼之前,我們需要先了解 RACChannelTerminal 這個(gè)類。之所以需要了解它,是因?yàn)?RACChannel 有兩個(gè)重要屬性 leadingTerminal、followingTerminal,它們分別代表了 RACChannel 的兩端,是實(shí)現(xiàn) RACChannel 的關(guān)鍵,而這兩個(gè)屬性都是 RACChannelTerminal 類型。
RACChannelTerminal 類定義如下:
@interface RACChannelTerminal<ValueType> : RACSignal<ValueType> <RACSubscriber>
- (instancetype)init __attribute__((unavailable("Instantiate a RACChannel instead")));
// Redeclaration of the RACSubscriber method. Made in order to specify a generic type.
- (void)sendNext:(nullable ValueType)value;
@end
從定義可以看出 RACChannelTerminal 繼承自RACSignal ,說明它可以被訂閱,同時(shí)實(shí)現(xiàn)了 RACSubscriber 協(xié)議,說明它可以發(fā)送消息。接下來(lái)看看RACChannelTerminal 的具體實(shí)現(xiàn):
RACChannelTerminal 實(shí)現(xiàn):
@implementation RACChannelTerminal
#pragma mark Lifecycle
- (instancetype)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal {
NSCParameterAssert(values != nil);
NSCParameterAssert(otherTerminal != nil);
self = [super init];
// 初始化兩個(gè)端點(diǎn)屬性
_values = values;
_otherTerminal = otherTerminal;
return self;
}
#pragma mark RACSignal
// 訂閱時(shí),實(shí)際上被訂閱的是self.values信號(hào)
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
return [self.values subscribe:subscriber];
}
#pragma mark <RACSubscriber>
// 發(fā)送時(shí),實(shí)際上是用self.otherTerminal 來(lái)發(fā)送消息
- (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
在初始化時(shí),RACChannelTerminal 需要傳入 values 和 otherTerminal 兩個(gè)值,其中 values、otherTerminal 分別表示 RACChannelTerminal 的兩個(gè)端點(diǎn)。在訂閱者調(diào)用 -subscribeNext: 等方法發(fā)起訂閱時(shí),實(shí)際上訂閱的是self.values 信號(hào);如果向當(dāng)前端點(diǎn)發(fā)送消息,會(huì)使用 self.otherTerminal 來(lái)發(fā)送消息。由于不是使用 self.values 的訂閱者來(lái)發(fā)送消息,因此,self.values 也就收不到 RACChannelTerminal 發(fā)送的消息。原理圖如下:

2、RACChannel
了解了 RACChannelTerminal 之后,我們?cè)賮?lái)看 RACChannel 的實(shí)現(xiàn),從源碼可以看出 RACChannel 有兩個(gè)屬性leadingTerminal、followingTerminal,他們分別代表了 RACChannel 的兩端,這兩個(gè)屬性都是 RACChannelTerminal 類型。
@interface RACChannel<ValueType> : NSObject
@property (nonatomic, strong, readonly) RACChannelTerminal<ValueType> *leadingTerminal;
@property (nonatomic, strong, readonly) RACChannelTerminal<ValueType> *followingTerminal;
@end
接下來(lái),我們看 RACChannel 的具體實(shí)現(xiàn):
- (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;
}
可以看出,RACChannel 初始化的時(shí)候,實(shí)際上只創(chuàng)建了兩個(gè) RACReplaySubject 熱信號(hào)。初始化 _leadingTerminal 和 _followingTerminal 兩個(gè)屬性時(shí),只是交換了兩個(gè) RACReplaySubject 的順序,因?yàn)閮?RACReplaySubject 是熱信號(hào),它們既可以作為訂閱者,也可以接收其他對(duì)象發(fā)送的消息。通過 -ignoreValues 和 -subscribe: 方法,leadingSubject 和 followingSubject 兩個(gè)熱信號(hào)中產(chǎn)生的錯(cuò)誤會(huì)互相發(fā)送,目的是為了防止一邊發(fā)生了錯(cuò)誤,另一邊還繼續(xù)工作。原理圖如下:

RACChannel 內(nèi)部的雙箭頭表示這兩個(gè) RACReplaySubject 為同一個(gè)熱信號(hào),由于只創(chuàng)建了兩個(gè) RACReplaySubject 熱信號(hào),因此,在兩個(gè) RACChannelTerminal 中,只是交換了_values 和 _otherTerminal 的位置。
雙向綁定
- 使用 RACChannel 實(shí)現(xiàn)雙向綁定。我們需要在 _leadingTerminal 端和 _followingTerminal 端分別實(shí)現(xiàn)訂閱和發(fā)送。
RACChannel *channel = [[RACChannel alloc] init];
RAC(self, a) = channel.leadingTerminal;
[RACObserve(self, a) subscribe:channel.leadingTerminal];
RAC(self, b) = channel.followingTerminal;
[RACObserve(self, b) subscribe:channel.followingTerminal];
不過遺憾的是會(huì)出現(xiàn)堆棧溢出的錯(cuò)誤,為什么呢?因?yàn)?RACChannel 只是實(shí)現(xiàn)了雙向綁定,并沒有幫我們處理循環(huán)調(diào)用的問題。在這里A的改動(dòng)會(huì)影響B(tài),B的改動(dòng)也會(huì)影響A,就這樣無(wú)限循環(huán)下去。
RACKVOChannel
直接使用 RACChannel,可能會(huì)出現(xiàn)堆棧溢出的錯(cuò)誤。因此,我們需要打斷這種死循環(huán)。這時(shí)候,我們就需要使用 RACKVOChannel 來(lái)實(shí)現(xiàn)雙向綁定了。RACKVOChannel 繼承自 RACChannel。接下來(lái)看一下它的初始化:
- (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];
[self.leadingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -leadingTerminal", target, keyPath, nilValue];
[self.followingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -followingTerminal", target, keyPath, nilValue];
if (strongTarget == nil) {
[self.leadingTerminal sendCompleted];
return self;
}
// Observe the key path on target for changes and forward the changes to the
// terminal.
//
// Intentionally capturing `self` strongly in the blocks below, so the
// channel object stays alive while observing.
RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
// If the change wasn't triggered by deallocation, only affects the last
// path component, and ignoreNextUpdate is set, then it was triggered by
// this channel and should not be forwarded.
if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) {
[self destroyCurrentThreadData];
return;
}
[self.leadingTerminal sendNext:value];
}];
NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent;
NSArray *keyPathComponents = keyPath.rac_keyPathComponents;
NSUInteger keyPathComponentsCount = keyPathComponents.count;
NSString *lastKeyPathComponent = keyPathComponents.lastObject;
// Update the value of the property with the values received.
[[self.leadingTerminal
finally:^{
[observationDisposable dispose];
}]
subscribeNext:^(id x) {
// Check the value of the second to last key path component. Since the
// channel can only update the value of a property on an object, and not
// update intermediate objects, it can only update the value of the whole
// key path if this object is not nil.
NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target);
if (object == nil) return;
// Set the ignoreNextUpdate flag before setting the value so this channel
// ignores the value in the subsequent -didChangeValueForKey: callback.
[self createCurrentThreadData];
self.currentThreadData.ignoreNextUpdate = YES;
[object setValue:x ?: nilValue forKey:lastKeyPathComponent];
} error:^(NSError *error) {
NSCAssert(NO, @"Received error in %@: %@", self, error);
// Log the error if we're running with assertions disabled.
NSLog(@"Received error in %@: %@", self, error);
}];
// Capture `self` weakly for the target's deallocation disposable, so we can
// freely deallocate if we complete before then.
@weakify(self);
[strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
@strongify(self);
[self.leadingTerminal sendCompleted];
self.target = nil;
}]];
return self;
}
- 可以看出,初始化的時(shí)候,使用了
- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block監(jiān)聽了傳入的 target 的 keyPath。但是最重要的是以下發(fā)送信息的部分:
RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
// If the change wasn't triggered by deallocation, only affects the last
// path component, and ignoreNextUpdate is set, then it was triggered by
// this channel and should not be forwarded.
if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) {
[self destroyCurrentThreadData];
return;
}
[self.leadingTerminal sendNext:value];
}];
接收到 target 的 keyPath 改變消息后,并不會(huì)都 sendNext。而是先判斷self.currentThreadData.ignoreNextUpdate的值。如果為 true 會(huì)忽略sendNext并銷毀 self.currentThreadData。
初始化時(shí),還訂閱了 self.leadingTerminal 信號(hào),當(dāng)收到消息時(shí),會(huì)先執(zhí)行
[self createCurrentThreadData];并設(shè)置self.currentThreadData.ignoreNextUpdate = YES;再去設(shè)置 target 的屬性值,從上面可知self.currentThreadData.ignoreNextUpdate = YES;時(shí)不會(huì)調(diào)用sendNext,因此不會(huì)構(gòu)成無(wú)限循環(huán)。最終效果
target 對(duì)象的a屬性,如果收了訂閱消息,則會(huì)設(shè)置ignoreNextUpdate為YES,然后設(shè)置a的值為新的值,這時(shí)候會(huì)觸發(fā)a的KVO,但是由于ignoreNextUpdate為YES所以不會(huì)發(fā)出消息。如果手動(dòng)改變了a的值,這時(shí),會(huì)觸發(fā)a的KVO,但是由于ignoreNextUpdate為NO,所以會(huì)發(fā)出消息。
RACChannelTo
上面提到了實(shí)現(xiàn)了雙向綁定的各個(gè)類,那么如何實(shí)現(xiàn)真正的雙向綁定呢,其實(shí)一句就可以:
RACChannelTo(self, a) = RACChannelTo(self, b);
展開宏定義:
[[RACKVOChannel alloc] initWithTarget:self keyPath:@"a" nilValue:nil][@"followingTerminal"] = [[RACKVOChannel alloc] initWithTarget:self keyPath:@"b" nilValue:nil][@"followingTerminal"]
接下來(lái)看源碼RACKVOChannel (RACChannelTo) 的實(shí)現(xiàn):
- (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;
}
- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key {
NSCParameterAssert(otherTerminal != nil);
RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key];
[otherTerminal subscribe:selfTerminal];
[[selfTerminal skip:1] subscribe:otherTerminal];
}
可以看出 [[RACKVOChannel alloc] initWithTarget:self keyPath:@"a" nilValue:nil][@"followingTerminal"] = [[RACKVOChannel alloc] initWithTarget:self keyPath:@"b" nilValue:nil][@"followingTerminal"]的效果就是實(shí)現(xiàn)兩個(gè) followingTerminal 的雙向綁定。

RACChannel 擴(kuò)展
RAC庫(kù)對(duì)常用的組件都進(jìn)行了 RACChannel 擴(kuò)展,在 UIKit 中下面的組件都提供了使用 RACChannel 的接口,用來(lái)實(shí)現(xiàn)數(shù)據(jù)的雙向綁定。

示例
1、viewModel 與UITextField 雙向綁定。
RACChannelTo(self.viewModel, username) = self.usernameTextField.rac_newTextChannel;
2、屬性雙向綁定。
RACChannelTo(self, a) = RACChannelTo(self, b);
3、UITextField 雙向綁定。
[self.textField.rac_newTextChannel subscribe:self.anotherTextField.rac_newTextChannel];
[self.anotherTextField.rac_newTextChannel subscribe:self.textField.rac_newTextChannel];
總結(jié)
雖然雙向綁定原理稍微復(fù)雜一些,但是在使用的時(shí)候 ReactiveObjC 提供的API 已經(jīng)足夠簡(jiǎn)單了,非常方便我們實(shí)現(xiàn)視圖與模型的雙向綁定。