文章系列
《ReactiveCocoa 概述》
《RACSignal》
《RACDisposable》
《RACSubject、RACReplaySubject(内附冷信号和热信号的区别)》
《集合RACTuple、RACSequence》
《RAC 中的通知、代理、KVO, 基本事件、方法的监听》
《rac_liftSelector》
《RACMulticastConnection》
《RACCommand》
《RAC - 核心方法bind》
《RAC - 定时器》
《RACScheduler》
《RAC - 点击获取验证码 demo》
《RAC - 映射(Map & flattenMap)》
《RAC信号操作解释合集》
《RAC - 信号的生命周期》
-
发现问题: 一般情况下,信号被订阅多少次,信号创建时的block 就调用多少次
// 以网络请求的信号被多次订阅为例:
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"请求数据");
[subscriber sendNext:@"数据是回来啦"];
return nil;
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第一次订阅=%@", x);
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第二次订阅=%@", x);
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第三次订阅=%@", x);
}];
打印结果: 进行了三次网络请求
-
解决原因及方法: 如果创建信号的block 中代码性能开销很大并且重复执行结果相同,那么确保block 中的代码只被执行一次就很有意义,Multicast就是做的这个事情.
// RACMulticastConnection 其实是一个连接类,可以实现不管订阅多少次信号,信号的block 都只请求一次
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"请求数据");
[subscriber sendNext:@"数据是回来啦"];
return nil;
}];
// 将信号转成连接类
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];
注:结尾处必须使用[connection connect]
进行连接才有效果.
打印结果: RACMulticastConnection 连接类订阅信号, block 只调用一次
-
RACMulticastConnection 内部实现原理分析
通过 RACMulticastConnection *connection = [signalA publish];
点击查看publish方法 的实现, 如下(省略非关键代码):
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
- 在publish 方法中首先创建了一个subject.
- 通过
[self multicast:subject]
保存在connection 中
↓ multicast:
的内部实现↓
- (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
自身和 传递进来的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 其实本质是初始化方法时传递进来的subject
_signal = subject;
return self;
}
私有的源信号
sourceSignal
即: 没有进行public
操作的signal
本质为
RACSubject
的_signal
对象, 即:public
操作时创建的对象(对应注释1)
↓所以在连接类进行信号订阅的时候[connection.signal subscribeNext:^...]
, 本质是这样的↓
- 订阅 connection.signal 中的数据流时,其实只是向多播对象中的热信号 RACSubject 持有的数组中加入订阅者,而这时刚刚创建的 RACSubject 中并没有任何的消息。
↓只有在调用connect
方法之后,RACSubject
才会订阅源信号 sourceSignal
↓
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
- 这时, 源信号的 didSubscribe 代码块才会执行, 向 RACSubject 推送消息,消息向下继续传递到 RACSubject 所有的订阅者中。 Values-From-RACSignal-To-Subscribers
- -connect 方法通过 -subscribe: 实际上建立了 RACSignal 和 RACSubject 之间的连接,这种方式保证了 RACSignal 中的 didSubscribe 代码块只执行了一次。
- 所有的订阅者不再订阅原信号,而是订阅 RACMulticastConnection 持有的热信号 RACSubject,实现对冷信号的一对多传播
-
使用 RACReplaySubject 订阅源信号
虽然使用 -publish 方法已经能够解决大部分问题了,但是在 -connect 方法调用之后才订阅的订阅者并不能收到消息。
如何才能保存 didSubscribe 执行过程中发送的消息,并在 -connect 调用之后也可以收到消息?这时,我们就要使用 -multicast: 方法和 RACReplaySubject 来完成这个需求了。
↓具体操作就是讲publish
替换成replay
↓
RACMulticastConnection *connection = [signalA replay];
内部实现的思想也都类似.
除了 -replay 方法,RACSignal 中还定义了与 RACMulticastConnection 中相关的其它 -replay 方法:
- (RACSignal<ValueType> *)replay;
// 方法生成的 RACMulticastConnection 中热信号的容量为 1:
- (RACSignal<ValueType> *)replayLast;
// replayLazily 会在返回的信号被第一次订阅时,才会执行 -connect 方法:
- (RACSignal<ValueType> *)replayLazily;
.End
网友评论