美文网首页ReactiveCocoaiOS Developer
ReactiveCocoa使用之细说信号的订阅

ReactiveCocoa使用之细说信号的订阅

作者: Tangentw | 来源:发表于2016-12-10 09:33 被阅读494次

    前言

    我们在使用ReactiveCocoa的时候,对RACSignal以及RACSubject的订阅非常频繁,虽然订阅的代码写起来并不繁琐,但里面也会存在一些比较细的问题,这篇文章主要针对信号在订阅中出现的某些问题进行一系列的分析。

    RACSignal的重复订阅

    在这里我首先创建一个普通的信号:

    RACSignal *signalOne = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            static NSInteger flag = 0;
            [subscriber sendNext:@(flag++)];
            return nil;
        }];
    

    从这个信号的构建中我们可以看出,当程序每次执行这个信号的构建block时,里面设置的静态整数标识符都会被以信号事件的形式发射出去,并且每次被发射,它都会进行自增。

    下面我们来对这个信号进行重复订阅:

    [signalOne subscribeNext:^(NSNumber *x) {
        NSLog(@"Subscriber1--%ld",[x integerValue]);
    }];
        
    [signalOne subscribeNext:^(NSNumber *x) {
        NSLog(@"Subscriber2--%ld",[x integerValue]);
    }];
        
    [signalOne subscribeNext:^(NSNumber *x) {
        NSLog(@"Subscriber3--%ld",[x integerValue]);
    }];
    

    在这里我创建了三个订阅者来对这一个事件进行重复订阅,并且在订阅block中进行输入事件的打印。

    跑起来,让我们看看控制台输出了什么:

    Subscriber1--0
    Subscriber2--1
    Subscriber3--2
    

    可见,当我们每次对这个信号进行订阅时,信号的创建block都会执行一次。

    如果这时候我有一个需求:不管有多少个订阅者,信号的构建block都只执行一次,订阅的事件都是这次构建block所发送的事件。我该怎么做?

    这里我可以使用两种方法:

    RACMulticastConnection

    使用方法:

    1. 在这里我创建一个信号,它里面的构建block跟上面的signalOne一样:
    RACSignal *signalTwo = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
           static NSInteger flag = 0;
           [subscriber sendNext:@(flag++)];
           return nil;
       }];
    
    1. 以新创建的信号为基础,创建一个RACMulticastConnection
    RACMulticastConnection *connection = [signalTwo publish];
    
    1. 利用新创建的RACMulticastConnection取出其信号属性进行订阅:
    RACSignal *connectionSignal = connection.signal;
       
       [connectionSignal subscribeNext:^(NSNumber *x) {
           NSLog(@"Subscriber1--%ld",[x integerValue]);
       }];
       
       [connectionSignal subscribeNext:^(NSNumber *x) {
           NSLog(@"Subscriber2--%ld",[x integerValue]);
       }];
       
       [connectionSignal subscribeNext:^(NSNumber *x) {
           NSLog(@"Subscriber3--%ld",[x integerValue]);
       }];
    
    1. 开始连接:
    [connection connect];
    

    注意最后一步不能够漏掉,仅仅实现了之前的三步并不会让connectionSignal发送事件,因为当你执行connect方法时,connection才会去订阅源信号,所以connectionSignal实质上其实是一个RACSubject,当源信号发送事件时,相应的connectionSignal会调用sendNext:

    现在运行程序,看看控制台打印出什么结果:

    Subscriber1--0
    Subscriber2--0
    Subscriber3--0
    

    可见,运用此方法,源信号signalTwo的创建block只执行了一次。


    Replay、ReplayLast、ReplayLazily

    这三个是RACSignal都可以使用的方法,它们的返回值也是一个信号,并且对于RACSignalreplayreplayLazily的效果一样,所以接下来我主要讲解replayreplayLast相关使用以及区别:

    1. 创建两个信号,它们的构建block与前面不同的是里面会再发送一次带有flag的事件:
    RACSignal *signalThree = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
           static NSInteger flag = 0;
           [subscriber sendNext:@(flag++)];
           [subscriber sendNext:@(flag++)];
           return nil;
       }];
    RACSignal *signalFour =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
           static NSInteger flag = 0;
           [subscriber sendNext:@(flag++)];
           [subscriber sendNext:@(flag++)];
           return nil;
       }];
    
    1. 基于前面创建的两个信号,利用replay以及replayLast分别再引出两个信号signalFivesignalSix
    RACSignal *signalFive = [signalThree replay];
       RACSignal *signalSix = [signalFour replayLast];
    
    1. 分别对signalFivesignalSix进行两次订阅:
       [signalFive subscribeNext:^(NSNumber *x) {
           NSLog(@"Subscriber1-1---%ld",[x integerValue]);
       }];
       [signalFive subscribeNext:^(NSNumber *x) {
           NSLog(@"Subscriber1-2---%ld",[x integerValue]);
       }];
       //  --------------------------------------
       [signalSix subscribeNext:^(NSNumber *x) {
           NSLog(@"Subscriber2-1---%ld",[x integerValue]);
       }];
       [signalSix subscribeNext:^(id x) {
           NSLog(@"Subscriber2-2---%ld",[x integerValue]);
       }];
       ```
    4.  运行程序,看看控制台的输出:
    
    

    Subscriber1-1---0
    Subscriber1-1---1
    Subscriber1-2---0
    Subscriber1-2---1


    Subscriber2-1---1
    Subscriber2-2---1

    
    由此可得结论:
    
    - 当我们使用`replay`处理信号后,不管订阅者有多少个,信号的构建block都只会执行一次,且信号的构建block里若有N次事件发送,订阅者就会接收到N次的事件。
    - 当我们使用`replayLast`处理信号后,不管订阅者有多少个,信号的构建block都只会执行一次,其不管信号的构建block里有多少次事件发送,订阅者都只会接收到信号最后一次发送的事件。
    
    ## RACSubject的信号重接收订阅
    对于`RACSubject`的订阅来说,其有两种订阅顺序:
    
    - 对subject信号的订阅在其`sendNext:`之前
    - 对subject信号的订阅在其`sendNext:`之后
    
    对于第一种情况,若订阅者先订阅了subject,在之后subject才去发送事件,这样子的话所有订阅者都会接收到此subject发送的全部事件,所以对于这种情况我们不进行讨论。
    
    我们现在针对第二种情况对某个`RACSubject`进行普通的订阅:
    
    1. 在这里我们创建一个`RACSubject`,并让他先发送三个事件,然后我们对它进行订阅:
    
     ```objc
    RACSubject *subjectOne = [RACSubject subject];
    [subjectOne sendNext:@(0)];
    [subjectOne subscribeNext:^(NSNumber *x) {
        NSLog(@"Subscribe--%ld",[x integerValue]);
    }];
    
    1. 让我们让程序跑起来,我们会看到控制台并无打印。

    可见,对于RACSubject,若我们在其发送事件之后进行普通的订阅,订阅者并不会收到它发送的事件。

    若我们想做到在RACSubject发送事件后进行订阅,且又能接收到它在被订阅前发送的事件,我们应该怎么办呢?

    这时候也是有两个方法供君选择:

    ReplayReplayLastReplayLazily

    在文章前面我也说到过这三个方法也可用于RACSignal的重复订阅,其实在RACSubject中这三个方法的效果跟RACSignal也非常相似,现在就让我们来看一下:
    首先,我们先对replayreplayLast进行分析:

    1. 创建一个RACSubject,利用replayreplayLast从它里面引出两个信号,并让它发送两个带有整数的事件:
       RACSubject *subjectTwo = [RACSubject subject];
       RACSignal *replaySignal = subjectTwo.replay;
       RACSignal *replayLastSignal = subjectTwo.replayLast;
       [subjectTwo sendNext:@(0)];
       [subjectTwo sendNext:@(1)];
    

    注意,利用replayreplayLast引出信号的这两个语句不能够写在subject发送事件之后,不然的话订阅者就无法接收到那些事件了。

    1. 分别订阅两个引出信号:
    [replaySignal subscribeNext:^(NSNumber *x) {
           NSLog(@"One--%ld",[x integerValue]);
       }];
       [replayLastSignal subscribeNext:^(NSNumber *x) {
           NSLog(@"Two--%ld",[x integerValue]);
       }];
    
    1. 现在,我们运行程序,看看控制台输出了什么:
    One--0
    One--1
    ---------
    Two--1
    

    由此可见:

    • 当我们订阅被replay进行处理的RACSubject后,订阅者会收到它在订阅前RACSubject发送的全部事件。
    • 当我们订阅被replayLast进行处理的RACSubject后,订阅者会收到它在订阅前RACSubject最后一次发送的事件。

    现在我们再看看replayLazily

    其实replayLazilyreplay是非常相似的,都可以让订阅者接收到RACSubject被订阅前发送的所有事件,但是,replayLazily比起后者多出了"Lazily",我们可从它的命名知道它是具有"懒惰性"的,现在我就通过一个例子来对比一下replayLazilyreplay

    1. 我们先创建一个RACSubject,利用replayreplayLazily从它里面引出两个信号,并让它发送一个带有整数"0"的事件:
    RACSubject *subjectThree = [RACSubject subject];
       RACSignal *replaySignal = subjectThree.replay;
       RACSignal *replayLazilySignal = subjectThree.replayLazily;
       [subjectThree sendNext:@(0)];
    
    1. 然后我们分别对两个引出信号进行订阅:
    [replaySignal subscribeNext:^(NSNumber *x) {
           NSLog(@"One--%ld",[x integerValue]);
       }];
       [replayLazilySignal subscribeNext:^(NSNumber *x) {
           NSLog(@"Two--%ld",[x integerValue]);
       }];
    
    1. 现在我们运行程序,查看控制台的打印:
    One--0
    

    可见,与replay相比,使用replayLazily引出的信号在订阅后没有接收到在其订阅前RACSubject发送的事件。

    现在我们将代码改一改,在subject发送事件的前面再插一个订阅的语句,最终就是这样子:

    RACSubject *subjectThree = [RACSubject subject];
    RACSignal *replaySignal = subjectThree.replay;
    RACSignal *replayLazilySignal = subjectThree.replayLazily;
    [replayLazilySignal subscribeNext:^(NSNumber *x) {
        NSLog(@"Zero--%ld",[x integerValue]);
    }];
    [subjectThree sendNext:@(0)];
    

    其他的代码我们不需要修改,然后现在我们运行一下程序,看看控制台的输出:

    Zero--0
    One--0
    Two--0
    

    现在我们可以看到,事件发送前后的replayLazily信号都可以接收到事件,由此我们可以得出结论:

    利用replayLazilyRACSubject里引出的信号,存在"懒惰性",这个信号开始可以理解为一个"冷信号",需要在RACSubject发送事件前先被订阅一次,让其激活成为"热信号",然后才可以在RACSubject发送事件后再进行订阅,这样子就能够接收到订阅前subject发送的信号。


    RACReplaySubject

    RACReplaySubject可以充当上面的replay以及replayLast方法,接收订阅前发送的全部事件或者最后一个事件,不仅如此,你还能设置让它接收特定数量的事件,下面我就展示一下它的使用:

    1. 我们用另一种构建方法构建一个RACReplaySubject,通过设置capacity来限定它接收重接收事件的数量,并让它发送三个事件:
       RACReplaySubject *replaySubject = [RACReplaySubject replaySubjectWithCapacity:2];
                                         //  [RACReplaySubject subject];
       [replaySubject sendNext:@(0)];
       [replaySubject sendNext:@(1)];
       [replaySubject sendNext:@(3)];
    
    1. 订阅replaySubject:
    [replaySubject subscribeNext:^(NSNumber *x) {
           NSLog(@"Subscriber--%ld",[x integerValue]);
       }];
    
    1. 运行程序,查看控制台打印:
    Subscriber--1
    Subscriber--3
    

    可见,RACReplaySubject会接收最新的两条事件。

    参考资料

    Comparing replay, replayLast, and replayLazily

    最快让你上手ReactiveCocoa之基础篇

    相关文章

      网友评论

      本文标题:ReactiveCocoa使用之细说信号的订阅

      本文链接:https://www.haomeiwen.com/subject/gwwymttx.html