A multicast connection encapsulates the idea of sharing one subscription to a signal to many subscribers. This is most often needed if the subscription to the underlying signal involves side-effects or shouldn't be called more than once.
多播連接 封裝了多個訂閱者共享同一份訂閱的這樣一個概念沟涨,通常來說营罢,當(dāng)對底層的 signal 的訂閱會產(chǎn)生很多副作用或者這個訂閱不應(yīng)該被多次調(diào)用時才會需要它。
源碼的注釋只給了我們一個很模糊的概念鹤啡,下面我們通過閱讀源碼來看看是如何實(shí)現(xiàn)的并且它究竟在實(shí)戰(zhàn)中有什么用處。
首先我們看頭文件:
@interface RACMulticastConnection : NSObject
@property (nonatomic, strong, readonly) RACSignal *signal;
- (RACDisposable *)connect;
...
@end
signal 屬性就是用于多播的信號粤策,connect 用于和底層的信號建立聯(lián)系岛请,先不管 autoconnect秕硝。
接著看實(shí)現(xiàn)文件:
@interface RACMulticastConnection () {
RACSubject *_signal;
int32_t volatile _hasConnected;
}
@property (nonatomic, readonly, strong) RACSignal *sourceSignal;
@property (strong) RACSerialDisposable *serialDisposable;
@end
@implementation RACMulticastConnection
#pragma mark Lifecycle
- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
NSCParameterAssert(source != nil);
NSCParameterAssert(subject != nil);
self = [super init];
if (self == nil) return nil;
_sourceSignal = source;
_serialDisposable = [[RACSerialDisposable alloc] init];
_signal = subject;
return self;
}
#pragma mark Connecting
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
...
}
_signal
是一個私有的 ivar,類型是 RACSubject捷枯,它是 RACSignal 的子類滚秩,所以 signal property 背后的 ivar 就是這個 _signal。
_hasConnected
是一個標(biāo)志量淮捆,用于標(biāo)識是否已經(jīng)建立連接郁油,無論調(diào)用多少次 connect 都只會建立一次連接本股。
sourceSignal
是源信號,這個信號是外部傳入的那個我們自己創(chuàng)建的信號桐腌。
initWithSourceSignal:subject:
是初始化方法拄显,第一個參數(shù)是用于多播的源信號,第二個參數(shù)是用于建立連接的信號案站,方法內(nèi)部實(shí)現(xiàn)很簡單躬审,就是對屬性進(jìn)行賦值。
connect
方法首先判斷是否已經(jīng)建立連接蟆盐,如果沒有的話承边,就調(diào)用 sourceSignal 的 subscribe 方法,將 _signal 注冊到源信號石挂。
在 RAC 中用到 RACMulticastConnection 的地方是在 RACSignal 的 operation 分類中的這些方法中:
- publish
- multicast:
- replay
- replayLast
- replayLazily
我們以 replay 方法舉例:
- (RACSignal *)replay {
RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];
RACMulticastConnection *connection = [self multicast:subject];
[connection connect];
return connection.signal;
}
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
return connection;
}
首先創(chuàng)建一個 RACReplaySubject博助,這個 subject 可以將值流中的值保存下來,每次訂閱都會將所有保存的值發(fā)出去痹愚,然后通過 multicast: 方法傳入剛才創(chuàng)建的 subject 新建一個 RACMulticastConnection富岳,multicast: 方法也很簡單,直接調(diào)用的是 RACMulticastConnection 的初始化方法拯腮,分別傳入 self 和 subject窖式,最后調(diào)用 connect 方法。
通過(一)动壤,創(chuàng)建一個 signal 實(shí)際上內(nèi)部是創(chuàng)建的 RACDynamicSignal萝喘,并把 didSubscribe block 保存在 signal 內(nèi)部。當(dāng) signal 調(diào)用以 subscribe 開頭的方法時狼电,會新建一個 RACSubscriber 然后調(diào)用 subscribe 方法。
在這里弦蹂,我們沒有調(diào)用 signal 的以 subscribe 開頭的方法肩碟,而是在 connect 方法內(nèi)部直接調(diào)用 signal 的 subscribe 方法,該方法會調(diào)用 didSubscribe 方法凸椿,傳入的 subject 就直接作為訂閱者來接收值削祈,并且這些值被保存了下來。
接下來回到 replay 方法脑漫,它返回的是 connection.signal髓抑,前面已經(jīng)提到這個 signal 就是之前創(chuàng)建的 subject,之后每一次對這個 signal 進(jìn)行訂閱优幸,都不會再去調(diào)用源信號的 didSubscirbe 方法吨拍,而是去調(diào)用 subject 重寫的 subscribe: 方法,這個方法會調(diào)用 subject 所有的 subscriber 的 sendNext: 方法网杆,把保存的值都傳出去羹饰,最后能夠經(jīng)過一系列的轉(zhuǎn)換后調(diào)用到 nextBlock伊滋,從而結(jié)束整個流程。
到這里基本分析完了 RACMulticastConnection 是怎么做的队秩,接下來看看它能做什么笑旺。
replay 會讓源信號只被訂閱一次,并把所有的值保存下來馍资,也即是 didSubscribe 只被調(diào)用一次筒主,之后的每次訂閱都會把保存的所有值發(fā)送出去。
replayLast 和 replay 很類似鸟蟹,其實(shí)只是做了一個限制乌妙,保存最近的一個值。
replayLazily 需要好好分析一下戏锹,看下面的源碼:
- (RACSignal *)replayLazily {
RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];
return [[RACSignal
defer:^{
[connection connect];
return connection.signal;
}]
setNameWithFormat:@"[%@] -replayLazily", self.name];
}
依舊是把 self 作為源信號冠胯,新建了一個 RACReplaySubject 作為用于多播的 signal,不同的是锦针,這里用 defer 新建了一個 signal荠察,并且在 defer block 里面才建立連接。
defer 有什么用呢奈搜?繼續(xù)看源碼:
+ (RACSignal *)defer:(RACSignal * (^)(void))block {
NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [block() subscribe:subscriber];
}] setNameWithFormat:@"+defer:"];
}
defer 會創(chuàng)建一個 signal悉盆,在傳入的 didSubscribe block 里面,首先讓源信號和 subject 建立聯(lián)系馋吗,然后讓訂閱者訂閱這個 subject焕盟。
replayLazily 很明顯和之前的兩個不同的是,只有第一次新的 subscriber 訂閱源 signal 時宏粤,內(nèi)部的 subject 才會和源 signal 建立聯(lián)系脚翘,didSubscribe 才會被調(diào)用,值才會被保存下來并且發(fā)送出去绍哎,而之后的訂閱會因?yàn)橐呀?jīng)建立聯(lián)系来农,_hasConnected 值為 1,所以不會再調(diào)用源信號的 subscribe崇堰,而是直接發(fā)送已經(jīng)保存的值沃于。
這樣解釋下來感覺挺亂的,關(guān)于它們?nèi)齻€的區(qū)別海诲,如果想看比較好的形式化的解釋繁莹,請移步看這篇文章。
總的來說特幔,RACMulticastConnection 是為了實(shí)現(xiàn)多次訂閱而只存在一次實(shí)際訂閱過程而創(chuàng)造的類咨演,底部的實(shí)現(xiàn)就是用一個 subject 來訂閱源信號,然后將這個 subject 作為信號暴露出去供其他訂閱者訂閱蚯斯。