RACMulticastConnection

文章系列
《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  _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  _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;
}
  1. 在publish 方法中首先创建了一个subject.
  2. 通过[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;
}
  1. RACMulticastConnection的初始化方法alloc init操作保存signal自身和 传递进来的subject.

RACMulticastConnection 是如何保存signal subject???

RACMulticastConnection类属性

- (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;
}
  1. 私有的源信号sourceSignal 即: 没有进行public 操作的signal
  2. 本质为RACSubject_signal对象, 即:public操作时创建的对象(对应注释1)

↓所以在连接类进行信号订阅的时候[connection.signal subscribeNext:^...], 本质是这样的↓

SubscribeNext-To-RACSubject-Before-Connect

  1. 订阅 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;
}
  1. 这时, 源信号的 didSubscribe 代码块才会执行, 向 RACSubject 推送消息,消息向下继续传递到 RACSubject 所有的订阅者中。
    Values-From-RACSignal-To-Subscribers
  2. -connect 方法通过 -subscribe: 实际上建立了 RACSignal 和 RACSubject 之间的连接,这种方式保证了 RACSignal 中的 didSubscribe 代码块只执行了一次。
  3. 所有的订阅者不再订阅原信号,而是订阅 RACMulticastConnection 持有的热信号 RACSubject,实现对冷信号的一对多传播
  • 使用 RACReplaySubject 订阅源信号

虽然使用 -publish 方法已经能够解决大部分问题了,但是在 -connect 方法调用之后才订阅的订阅者并不能收到消息。

如何才能保存 didSubscribe 执行过程中发送的消息,并在 -connect 调用之后也可以收到消息?这时,我们就要使用 -multicast: 方法和 RACReplaySubject 来完成这个需求了。

↓具体操作就是讲publish替换成replay

RACMulticastConnection *connection = [signalA replay];

内部实现的思想也都类似.

除了 -replay 方法,RACSignal 中还定义了与 RACMulticastConnection 中相关的其它 -replay 方法:

    - (RACSignal *)replay;
    // 方法生成的 RACMulticastConnection 中热信号的容量为 1:
    - (RACSignal *)replayLast;
    // replayLazily 会在返回的信号被第一次订阅时,才会执行 -connect 方法:
    - (RACSignal *)replayLazily;

.End

你可能感兴趣的:(RACMulticastConnection)