ReactiveCocoa 学习笔记(二)之 RACMulticastConnection

A multicast connection encapsulates the idea of sharing one subscription to a signal to many subscribers. This is most often needed if the subscription to the underlying signal involves side-effects or shouldn't be called more than once.

多播连接 封装了多个订阅者共享同一份订阅的这样一个概念,通常来说,当对底层的 signal 的订阅会产生很多副作用或者这个订阅不应该被多次调用时才会需要它。

源码的注释只给了我们一个很模糊的概念,下面我们通过阅读源码来看看是如何实现的并且它究竟在实战中有什么用处。

首先我们看头文件:

@interface RACMulticastConnection : NSObject

@property (nonatomic, strong, readonly) RACSignal *signal;
- (RACDisposable *)connect;
...

@end

signal 属性就是用于多播的信号,connect 用于和底层的信号建立联系,先不管 autoconnect。

接着看实现文件:

@interface RACMulticastConnection () {
 RACSubject *_signal;
 int32_t volatile _hasConnected;
}

@property (nonatomic, readonly, strong) RACSignal *sourceSignal;
@property (strong) RACSerialDisposable *serialDisposable;
@end

@implementation RACMulticastConnection

#pragma mark Lifecycle

- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
 NSCParameterAssert(source != nil);
 NSCParameterAssert(subject != nil);

 self = [super init];
 if (self == nil) return nil;

 _sourceSignal = source;
 _serialDisposable = [[RACSerialDisposable alloc] init];
 _signal = subject;
 
 return self;
}

#pragma mark Connecting

- (RACDisposable *)connect {
 BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

 if (shouldConnect) {
  self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
 }

 return self.serialDisposable;
}
...
}

_signal 是一个私有的 ivar,类型是 RACSubject,它是 RACSignal 的子类,所以 signal property 背后的 ivar 就是这个 _signal。

_hasConnected 是一个标志量,用于标识是否已经建立连接,无论调用多少次 connect 都只会建立一次连接。

sourceSignal 是源信号,这个信号是外部传入的那个我们自己创建的信号。

initWithSourceSignal:subject: 是初始化方法,第一个参数是用于多播的源信号,第二个参数是用于建立连接的信号,方法内部实现很简单,就是对属性进行赋值。

connect 方法首先判断是否已经建立连接,如果没有的话,就调用 sourceSignal 的 subscribe 方法,将 _signal 注册到源信号。

在 RAC 中用到 RACMulticastConnection 的地方是在 RACSignal 的 operation 分类中的这些方法中:

  • publish
  • multicast:
  • replay
  • replayLast
  • replayLazily

我们以 replay 方法举例:

- (RACSignal *)replay {
 RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];

 RACMulticastConnection *connection = [self multicast:subject];
 [connection connect];

 return connection.signal;
}

- (RACMulticastConnection *)multicast:(RACSubject *)subject {
 [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
 RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
 return connection;
}

首先创建一个 RACReplaySubject,这个 subject 可以将值流中的值保存下来,每次订阅都会将所有保存的值发出去,然后通过 multicast: 方法传入刚才创建的 subject 新建一个 RACMulticastConnection,multicast: 方法也很简单,直接调用的是 RACMulticastConnection 的初始化方法,分别传入 self 和 subject,最后调用 connect 方法。

通过(一),创建一个 signal 实际上内部是创建的 RACDynamicSignal,并把 didSubscribe block 保存在 signal 内部。当 signal 调用以 subscribe 开头的方法时,会新建一个 RACSubscriber 然后调用 subscribe 方法。

在这里,我们没有调用 signal 的以 subscribe 开头的方法,而是在 connect 方法内部直接调用 signal 的 subscribe 方法,该方法会调用 didSubscribe 方法,传入的 subject 就直接作为订阅者来接收值,并且这些值被保存了下来。

接下来回到 replay 方法,它返回的是 connection.signal,前面已经提到这个 signal 就是之前创建的 subject,之后每一次对这个 signal 进行订阅,都不会再去调用源信号的 didSubscirbe 方法,而是去调用 subject 重写的 subscribe: 方法,这个方法会调用 subject 所有的 subscriber 的 sendNext: 方法,把保存的值都传出去,最后能够经过一系列的转换后调用到 nextBlock,从而结束整个流程。

到这里基本分析完了 RACMulticastConnection 是怎么做的,接下来看看它能做什么。

replay 会让源信号只被订阅一次,并把所有的值保存下来,也即是 didSubscribe 只被调用一次,之后的每次订阅都会把保存的所有值发送出去。

replayLast 和 replay 很类似,其实只是做了一个限制,保存最近的一个值。

replayLazily 需要好好分析一下,看下面的源码:

- (RACSignal *)replayLazily {
 RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];
 return [[RACSignal
  defer:^{
   [connection connect];
   return connection.signal;
  }]
  setNameWithFormat:@"[%@] -replayLazily", self.name];
}

依旧是把 self 作为源信号,新建了一个 RACReplaySubject 作为用于多播的 signal,不同的是,这里用 defer 新建了一个 signal,并且在 defer block 里面才建立连接。

defer 有什么用呢?继续看源码:

+ (RACSignal *)defer:(RACSignal * (^)(void))block {
 NSCParameterAssert(block != NULL);

 return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
  return [block() subscribe:subscriber];
 }] setNameWithFormat:@"+defer:"];
}

defer 会创建一个 signal,在传入的 didSubscribe block 里面,首先让源信号和 subject 建立联系,然后让订阅者订阅这个 subject。

replayLazily 很明显和之前的两个不同的是,只有第一次新的 subscriber 订阅源 signal 时,内部的 subject 才会和源 signal 建立联系,didSubscribe 才会被调用,值才会被保存下来并且发送出去,而之后的订阅会因为已经建立联系,_hasConnected 值为 1,所以不会再调用源信号的 subscribe,而是直接发送已经保存的值。

这样解释下来感觉挺乱的,关于它们三个的区别,如果想看比较好的形式化的解释,请移步看这篇文章

总的来说,RACMulticastConnection 是为了实现多次订阅而只存在一次实际订阅过程而创造的类,底部的实现就是用一个 subject 来订阅源信号,然后将这个 subject 作为信号暴露出去供其他订阅者订阅。

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

推荐阅读更多精彩内容

  • 先贴上我看的博客,大部分内容来自以下博客 入门教程 sunnyxx的博客,共四篇 美团的四篇博客 一篇关于repl...
    Auther丶阅读 3,302评论 1 40
  • 1.ReactiveCocoa常见操作方法介绍1.1 ReactiveCocoa操作须知所有的信号(RACSign...
    IIronMan阅读 2,583评论 2 17
  • RAC使用测试Demo下载:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees阅读 6,353评论 3 10
  • 前言由于时间的问题,暂且只更新这么多了,后续还会持续更新本文《最快让你上手ReactiveCocoa之进阶篇》,目...
    Karos_凯阅读 1,725评论 0 6
  • 1、有一段时间爸爸迫切地想要女儿背会《朱子家训》。 有一次女儿在背诵中,​卡在那里想不出下一句,爸爸很生气,愤怒地...
    韩悦沐_疗愈_成长阅读 274评论 0 2