ReactiveCocoa使用之细说信号的订阅

前言

我们在使用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之基础篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容