本系列从
RACSignal
出发,遇到坑现踩现填,想到什么写什么。有写的不对的地方,欢迎指正。
RACSignal
通常使用这个方法?来初始化一个 Signal
+ (RACSignal<ValueType> *)createSignal:(RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber))didSubscribe;
复制代码
但它实际上返回的是一个 RACSignal
的子类 RACDynamicSignal
。其中 didSubscribe
参数是一个 block,每当信号被订阅的时候,这个 block 都会被调用。?
// RACSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
// 实则创建的是 RACDynamicSignal
return [RACDynamicSignal createSignal:didSubscribe];
}
复制代码
// RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
// 保存一份 block 的 copy
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
复制代码
再来看这个 block 的类型?
RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber)
复制代码
它会被传入一个 id<RACSubscriber>
类型的参数,你可以通过调用这个 subscriber
的 -sendNext:
、-sendError:
和 -sendCompleted
方法来手动触发你传给 -subscribeNext:
、-subcribeError:
和 -subscribeCompleted:
的 block 。 除此之外,这个 block 会返回一个 RACDisposable
类型的实例,用于操作即将被销毁的信号。在 RACDisposable
的 -dispose
方法被调用后,Signal 将无法再向 Subscriber 发送事件。
通过这个方法?来获得一个立即执行 block 的 Signal
+ (RACSignal<ValueType> *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block;
复制代码
它的实现实际上调用了另一个方法?
+ (RACSignal<ValueType> *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block;
复制代码
也就是获取一个“惰性的” Signal,他的 block 只有在被首次订阅的时候才会调用,但是会为新 Subscriber 重播它错过的事件。 比如下面这个 Demo?
RACSignal *signalLazy = [RACSignal startLazilyWithScheduler:[RACScheduler mainThreadScheduler] block:^(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"signalLazy");
[subscriber sendNext:@"a"];
[subscriber sendNext:@"b"];
[subscriber sendNext:@"c"];
[subscriber sendCompleted];
}];
// 首次订阅
[signalLazy subscribeNext:^(NSString * _Nullable x) {
NSLog(@"lazySignal: %@", x);
}];
// 再次订阅
[signalLazy subscribeNext:^(NSString * _Nullable x) {
NSLog(@"lazySignal: %@", x);
}];
// output:
// signalLazy
// lazySignal: a
// lazySignal: b
// lazySignal: c
// lazySignal: a
// lazySignal: b
// lazySignal: c
复制代码
从上面的 Demo 可以看出,即使被订阅了两次,但是 Lazy Signal 的 block 只被调用了一次,而且它还贴心地为第二个 Subscriber 重播了之前错过的事件。 我们来看一下它的实现?
+ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block {
NSCParameterAssert(scheduler != nil);
NSCParameterAssert(block != NULL);
// 创建一个 RACMulticastConnection,用于在源 Signal 与 RACReplaySubject 之间建立连接
RACMulticastConnection *connection = [[RACSignal
createSignal:^ id (id<RACSubscriber> subscriber) {
// 直接调用 block,且不返回 Disposable
block(subscriber);
return nil;
}]
multicast:[RACReplaySubject subject]];
// 返回一个 Signal
return [[[RACSignal
createSignal:^ id (id<RACSubscriber> subscriber) {
// 这里的 connection.signal 就是上面的? RACReplaySubject
// 所以实际上新 subscriber 订阅的是 RACReplaySubject
[connection.signal subscribe:subscriber];
// 建立源 Signal 与 RACReplaySubject 之间的连接
// 本质就是订阅源 Signal,但只能订阅一次
[connection connect];
return nil;
}]
subscribeOn:scheduler]
setNameWithFormat:@"+startLazilyWithScheduler: %@ block:", scheduler];
}
复制代码
上面的实现涉及到了两个核心类:
RACMulticastConnection
:用于实现一对多的事件转发。比如 B 订阅了 A ,同时又有 C、D 也想订阅 A。这个时候如果有一个 E 来专门负责从 A 到 所有 Subscriber 的转发,就会方便很多,所有 Subscriber 只需要订阅 E 就可以了,不需要关心 A 。
RACReplaySubject
:顾名思义,用于实现对事件的重播。这个类可以记录自己所发出的所有事件,在有新的 Subscriber 订阅时,会把所有事件都重播给这个新 Subscriber 。
实现的重点在于,Subscriber 订阅的并不是源 Signal 本身,而是有重播功能的 RACReplaySubject
,当有订阅时才去调用 -connect
,所以实现了“惰性”。那么问题来了:明明订阅的是 RACReplaySubject
为什么还会走一次源 Signal 的 block?关于这个问题,其实从上面的注释已经给出了简短的解答。
// 这里的 connection.signal 就是上面的? RACReplaySubject
// 所以实际上新 subscriber 订阅的是 RACReplaySubject
[connection.signal subscribe:subscriber];
// 建立源 Signal 与 RACReplaySubject 之间的连接
// 本质就是订阅源 Signal,但只能订阅一次
[connection connect];
复制代码
每当有新的 Subscriber 订阅这个 Signal 时,会先将 Subscriber 传给 connection.signal
也就是 RACReplaySubject
,这会使 connection.signal
开始重播已有的事件;随后调用 -connect
也就是让 connection.signal
订阅 sourceSignal
,见 -connect
方法的实现?
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
// 如果已经连接,则不会再订阅
// 也就是为什么源 Signal 的 block 只会调用一次
if (shouldConnect) {
// _signal 在内部实际上是一个 RACSubject
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
复制代码
由此可见,当第一个 Subscriber 订阅时,由于 _signal
还没有记录过任何事件,所以 [connection.signal subscribe:subscriber]
没有发出任何事件。但由于是第一次 -connect
,所以会调用源 Signal 的 block 并发出相应的事件给 connection.signal
来记录。到了后续的 Subscriber 订阅时,connection.signal
会重播源 Signal 的事件,并且由于已经连接, -connect
不会再生效了,所以不会再调用源 Signal 的 block 。
注意:
startLazilyWithScheduler:block:
方法返回的 Signal ,即使外部 Subscriber 对它进行了 dispose ,也不会触发其内部的 Subject 与 源 Signal 之间的 dispose。
看完了 +startLazilyWithScheduler:block:
,再回过头来看 +startEagerlyWithScheduler:block:
,之前提到它是由 +startLazilyWithScheduler:block:
实现的
+ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block {
NSCParameterAssert(scheduler != nil);
NSCParameterAssert(block != NULL);
RACSignal *signal = [self startLazilyWithScheduler:scheduler block:block];
// Subscribe to force the lazy signal to call its block.
[[signal publish] connect];
return [signal setNameWithFormat:@"+startEagerlyWithScheduler: %@ block:", scheduler];
}
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
复制代码
可以看出,“勤奋” 只是它的表象罢了,其骨子里还是个“死肥宅”。在它被创建的那一刻,就被手动与一个默认的 RACSubject
建立了连接,从而实现了立即订阅。
RACMulticastConnection
关于这个类的作用,上文已经提到:实现了一对多的事件转发。它的方法也很少,只有两个:
- (RACDisposable *)connect;
- (RACSignal<ValueType> *)autoconnect;
复制代码
-connect
已经讲过,下面我们来看 -autoconnect
,看头文件作者的注释,基本已可以了解它的功能:
Connects to the underlying signal when the returned signal is first subscribed to, and disposes of the subscription to the multicasted signal when the returned signal has no subscribers. If new subscribers show up after being disposed, they'll subscribe and then be immediately disposed of. The returned signal will never re-connect to the multicasted signal.
可见 -autoconnect
不仅具备只有首次连接才会触发订阅的逻辑,还可以在外部 Subscriber 数量为 0 的情况下自动 dispose 内部源 Signal 与 Subject 之间的订阅关系。而且一旦断开,就不会再重连。
来看一下它的实现?
- (RACSignal *)autoconnect {
__block volatile int32_t subscriberCount = 0;
return [[RACSignal
createSignal:^(id<RACSubscriber> subscriber) {
// 每当被订阅,计数器++
OSAtomicIncrement32Barrier(&subscriberCount);
// Subject 的 Disposable
RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
// 源 Signal 的 Disposable
RACDisposable *connectionDisposable = [self connect];
// 实现自身的 Disposable
return [RACDisposable disposableWithBlock:^{
// dispose Subject 的 Disposable
// 也就是断开外部 Subscriber 与 Subject 之间的订阅
[subscriptionDisposable dispose];
// 每当 dispose ,计数器--
if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
// 当外部 Subscriber 为 0 时
// dispose 源 Signal 的 Disposable
// 也就是断开 Subject 与源 Signal 之间的订阅
[connectionDisposable dispose];
}
}];
}]
setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}
复制代码