文章系列
《ReactiveCocoa 概述》
《RACSignal》
《RACDisposable》
《RACSubject漱竖、RACReplaySubject(內(nèi)附冷信號和熱信號的區(qū)別)》
《集合RACTuple禽篱、RACSequence》
《RAC 中的通知、代理馍惹、KVO, 基本事件躺率、方法的監(jiān)聽》
《rac_liftSelector》
《RACMulticastConnection》
《RACCommand》
《RAC - 核心方法bind》
《RAC - 定時器》
《RACScheduler》
《RAC - 點(diǎn)擊獲取驗(yàn)證碼 demo》
《RAC - 映射(Map & flattenMap)》
《RAC信號操作解釋合集》
《RAC - 信號的生命周期》
-
發(fā)現(xiàn)問題: 一般情況下,信號被訂閱多少次,信號創(chuàng)建時的block 就調(diào)用多少次
// 以網(wǎng)絡(luò)請求的信號被多次訂閱為例:
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"請求數(shù)據(jù)");
[subscriber sendNext:@"數(shù)據(jù)是回來啦"];
return nil;
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第一次訂閱=%@", x);
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第二次訂閱=%@", x);
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第三次訂閱=%@", x);
}];
打印結(jié)果:進(jìn)行了三次網(wǎng)絡(luò)請求
-
解決原因及方法: 如果創(chuàng)建信號的block 中代碼性能開銷很大并且重復(fù)執(zhí)行結(jié)果相同,那么確保block 中的代碼只被執(zhí)行一次就很有意義,Multicast就是做的這個事情.
// RACMulticastConnection 其實(shí)是一個連接類,可以實(shí)現(xiàn)不管訂閱多少次信號,信號的block 都只請求一次
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"請求數(shù)據(jù)");
[subscriber sendNext:@"數(shù)據(jù)是回來啦"];
return nil;
}];
// 將信號轉(zhuǎn)成連接類
RACMulticastConnection *connection = [signalA publish];
// 訂閱連接類信號
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"第一次連接類信號訂閱=%@", x);
}];
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"第二次連接類信號訂閱=%@", x);
}];
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"第三次連接類信號訂閱=%@", x);
}];
// 連接
[connection connect];
注:結(jié)尾處必須使用[connection connect]
進(jìn)行連接才有效果.
打印結(jié)果:RACMulticastConnection 連接類訂閱信號, block 只調(diào)用一次
-
RACMulticastConnection 內(nèi)部實(shí)現(xiàn)原理分析
通過 RACMulticastConnection *connection = [signalA publish];
點(diǎn)擊查看publish方法 的實(shí)現(xiàn), 如下(省略非關(guān)鍵代碼):
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
- 在publish 方法中首先創(chuàng)建了一個subject.
- 通過
[self multicast:subject]
保存在connection 中
↓ multicast:
的內(nèi)部實(shí)現(xiàn)↓
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
return connection;
}
- 通
RACMulticastConnection
的初始化方法alloc init
操作保存signal
自身和 傳遞進(jìn)來的subject
.
↓RACMulticastConnection 是如何保存
signal 和
subject???
↓
- (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
NSCParameterAssert(source != nil);
NSCParameterAssert(subject != nil);
self = [super init];
// 定義'只讀屬性'保存原始信號signal
_sourceSignal = source;
_serialDisposable = [[RACSerialDisposable alloc] init];
// 對外暴露屬性signal 其實(shí)本質(zhì)是初始化方法時傳遞進(jìn)來的subject
_signal = subject;
return self;
}
私有的源信號
sourceSignal
即: 沒有進(jìn)行public
操作的signal
本質(zhì)為
RACSubject
的_signal
對象, 即:public
操作時創(chuàng)建的對象(對應(yīng)注釋1)
↓所以在連接類進(jìn)行信號訂閱的時候[connection.signal subscribeNext:^...]
, 本質(zhì)是這樣的↓
- 訂閱 connection.signal 中的數(shù)據(jù)流時,其實(shí)只是向多播對象中的熱信號 RACSubject 持有的數(shù)組中加入訂閱者万矾,而這時剛剛創(chuàng)建的 RACSubject 中并沒有任何的消息悼吱。
↓只有在調(diào)用connect
方法之后,RACSubject
才會訂閱源信號 sourceSignal
↓
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
- 這時, 源信號的 didSubscribe 代碼塊才會執(zhí)行, 向 RACSubject 推送消息良狈,消息向下繼續(xù)傳遞到 RACSubject 所有的訂閱者中后添。
Values-From-RACSignal-To-Subscribers- -connect 方法通過 -subscribe: 實(shí)際上建立了 RACSignal 和 RACSubject 之間的連接,這種方式保證了 RACSignal 中的 didSubscribe 代碼塊只執(zhí)行了一次薪丁。
- 所有的訂閱者不再訂閱原信號遇西,而是訂閱 RACMulticastConnection 持有的熱信號 RACSubject,實(shí)現(xiàn)對冷信號的一對多傳播
-
使用 RACReplaySubject 訂閱源信號
雖然使用 -publish 方法已經(jīng)能夠解決大部分問題了窥突,但是在 -connect 方法調(diào)用之后才訂閱的訂閱者并不能收到消息努溃。
如何才能保存 didSubscribe 執(zhí)行過程中發(fā)送的消息,并在 -connect 調(diào)用之后也可以收到消息阻问?這時梧税,我們就要使用 -multicast: 方法和 RACReplaySubject 來完成這個需求了。
↓具體操作就是講publish
替換成replay
↓
RACMulticastConnection *connection = [signalA replay];
內(nèi)部實(shí)現(xiàn)的思想也都類似.
除了 -replay 方法称近,RACSignal 中還定義了與 RACMulticastConnection 中相關(guān)的其它 -replay 方法:
- (RACSignal<ValueType> *)replay;
// 方法生成的 RACMulticastConnection 中熱信號的容量為 1:
- (RACSignal<ValueType> *)replayLast;
// replayLazily 會在返回的信號被第一次訂閱時第队,才會執(zhí)行 -connect 方法:
- (RACSignal<ValueType> *)replayLazily;
.End