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.
多播连接 封装了多个订阅者共享同一份订阅的这样一个概念,通常来说,当对底层的 signal 的订阅会产生很多副作用或者这个订阅不应该被多次调用时才会需要它。
源码的注释只给了我们一个很模糊的概念,下面我们通过阅读源码来看看是如何实现的并且它究竟在实战中有什么用处。
首先我们看头文件:
@interface RACMulticastConnection : NSObject
@property (nonatomic, strong, readonly) RACSignal *signal;
- (RACDisposable *)connect;
...
@end
signal 属性就是用于多播的信号,connect 用于和底层的信号建立联系,先不管 autoconnect。
接着看实现文件:
@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
是一个标志量,用于标识是否已经建立连接,无论调用多少次 connect 都只会建立一次连接。
sourceSignal
是源信号,这个信号是外部传入的那个我们自己创建的信号。
initWithSourceSignal:subject:
是初始化方法,第一个参数是用于多播的源信号,第二个参数是用于建立连接的信号,方法内部实现很简单,就是对属性进行赋值。
connect
方法首先判断是否已经建立连接,如果没有的话,就调用 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;
}
首先创建一个 RACReplaySubject,这个 subject 可以将值流中的值保存下来,每次订阅都会将所有保存的值发出去,然后通过 multicast: 方法传入刚才创建的 subject 新建一个 RACMulticastConnection,multicast: 方法也很简单,直接调用的是 RACMulticastConnection 的初始化方法,分别传入 self 和 subject,最后调用 connect 方法。
通过(一),创建一个 signal 实际上内部是创建的 RACDynamicSignal,并把 didSubscribe block 保存在 signal 内部。当 signal 调用以 subscribe 开头的方法时,会新建一个 RACSubscriber 然后调用 subscribe 方法。
在这里,我们没有调用 signal 的以 subscribe 开头的方法,而是在 connect 方法内部直接调用 signal 的 subscribe 方法,该方法会调用 didSubscribe 方法,传入的 subject 就直接作为订阅者来接收值,并且这些值被保存了下来。
接下来回到 replay 方法,它返回的是 connection.signal,前面已经提到这个 signal 就是之前创建的 subject,之后每一次对这个 signal 进行订阅,都不会再去调用源信号的 didSubscirbe 方法,而是去调用 subject 重写的 subscribe: 方法,这个方法会调用 subject 所有的 subscriber 的 sendNext: 方法,把保存的值都传出去,最后能够经过一系列的转换后调用到 nextBlock,从而结束整个流程。
到这里基本分析完了 RACMulticastConnection 是怎么做的,接下来看看它能做什么。
replay 会让源信号只被订阅一次,并把所有的值保存下来,也即是 didSubscribe 只被调用一次,之后的每次订阅都会把保存的所有值发送出去。
replayLast 和 replay 很类似,其实只是做了一个限制,保存最近的一个值。
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 有什么用呢?继续看源码:
+ (RACSignal *)defer:(RACSignal * (^)(void))block {
NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [block() subscribe:subscriber];
}] setNameWithFormat:@"+defer:"];
}
defer 会创建一个 signal,在传入的 didSubscribe block 里面,首先让源信号和 subject 建立联系,然后让订阅者订阅这个 subject。
replayLazily 很明显和之前的两个不同的是,只有第一次新的 subscriber 订阅源 signal 时,内部的 subject 才会和源 signal 建立联系,didSubscribe 才会被调用,值才会被保存下来并且发送出去,而之后的订阅会因为已经建立联系,_hasConnected 值为 1,所以不会再调用源信号的 subscribe,而是直接发送已经保存的值。
这样解释下来感觉挺乱的,关于它们三个的区别,如果想看比较好的形式化的解释,请移步看这篇文章。
总的来说,RACMulticastConnection 是为了实现多次订阅而只存在一次实际订阅过程而创造的类,底部的实现就是用一个 subject 来订阅源信号,然后将这个 subject 作为信号暴露出去供其他订阅者订阅。