RAC基础学习一:信号和订阅者模式

参考:https://www.jianshu.com/p/4fee21fb05b3

我们在领略到RAC的强大和不可思议的时候,需要思考两个地方:1、它是如何实现的?第二个问题则更有难度:2、它是如何想到这样设计的? 这里我们先尝试研究第一个问题,它是如何实现的,分析主要的脉络。

在RAC里面,我们所有围绕的东西无非主体是这几样:信号(signal)、订阅者(subscriber)、还有关于信号的生产者实体、信号的消费者,这几个的关系。

优势:我们为什么使用RAC,因为它解耦太好了,除此之外,它简洁,配合MVVM能发挥出很大的作用等等。相信我们都写腻了对象之间的复杂通信、一大堆状态的创建和管理、越来越难维护的业务逻辑,这些就是RAC诞生的使命。

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber){ [subscriber sendNext:@(1)];

 [subscriber sendCompleted]; return nil; }];

1、createSignal好难啊;2、subscriber是什么?3、这个block什么时候调用?

 [signal subscribeNext:^(id x) {

if ([x boolValue]) { _navView.hidden = YES; }

else { _navView.hidden = NO;

[UIView animateWithDuration:.5 animations:^{ _navView.alpha = 1; }]; } }];

4、subscribeNext又是什么?5、这个block什么时候调用?


第一部分 订阅者和信号###

1、隐藏的订阅者

平时我们打交道的就是信号,但是总是说订阅,却不知道订阅到底是如何进行的,也无法解答上面的问题,让我们根据源码分析一下订阅过程。

首先来认识一个对象:订阅者(RACSubscriber)。 订阅者订阅信号,就是这么简单的一件事情。只不过框架隐藏了这个对象,我们也不必要和订阅者打交道,只需要告诉信号一件事情,那就是如果发送了数据(三种事件:next、complete、error),我们需要做什么事情(类似回调的概念)。

第一步是创建信号,看一下上面的第一段代码,createSignal类方法: 这里要说一下,信号RACSignal有一些子类,我们常用的是RACDynamicSignal和RACSubject,先不理会RACSubject。createSignal类方法创建的就是RACDynamicSignal对象。

-----RACDynamicSignal.h-----

@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(idsubscriber);

-----RACSignal.m-----

+ (RACSignal *)createSignal:(RACDisposable * (^)(idsubscriber))didSubscribe {

   return [RACDynamicSignal createSignal:didSubscribe];}

-----RACDynamicSignal.m-----

+ (RACSignal *)createSignal:(RACDisposable * (^)(idsubscriber))didSubscribe {

   RACDynamicSignal *signal = [[self alloc] init];

   signal->_didSubscribe = [didSubscribe copy];

   return [signal setNameWithFormat:@"+createSignal:"];}

我们可以发现,RACDynamicSignal有一个属性,名字叫didSubscribe的block对象。createSignal方法传递的block参数,就是赋值给didSubscribe属性。

 对于问题1,我们可以暂时这么回答,createSignal的意义是,创建一个signal对象,并且把参数赋值给signal的名为didSubscribe的属性,这个block的参数是subscriber,返回RACDisposable。

第二步是订阅信号,看一下第二段代码subscribeNext:

-----RACSubscriber.m-----

@property (nonatomic, copy) void (^next)(id value);

-----RACSignal.m------

 (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {

   NSCParameterAssert(nextBlock != NULL);

    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];

   return [self subscribe:o];}

-----RACDynamicSignal.m------

(RACDisposable *)subscribe:(id)subscriber {

NSCParameterAssert(subscriber != nil);

RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];

 subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

if (self.didSubscribe != NULL) {

 RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber);

[disposable addDisposable:innerDisposable]; }];

 [disposable addDisposable:schedulingDisposable]; }

 return disposable; }

我们可以看到,subscribeNext方法第一步是创建了一个RACSubscriber,也就是创建了一个订阅者,而且把subscribeNext的参数传递给RACSubscriber对象,RACSubscriber会把参数赋值给自己一个名为next的Block类型的属性,这里,我们可以回答上面第4个问题,subscribeNext方法创建一个订阅者,并且把block参数,传递给订阅者一个名字叫next的属性,block参数接收的是id类型,返回的是RACDisposable对象。接下来执行[self subscribe:o],也就是订阅操作。我们在看看订阅方法subscribe的实现:上面的代码很清晰,直接是self.didSubscribe(subscriber),我们可以知道,刚刚创建的subscriber对象,直接传递给上文中我们提到的signal的didSubscribe属性。这样,我们可以解释上面的第二个和第三个问题,subscriber就是didSubscribe的形参,block对象是在subscribeNext的时候执行的,刚刚的订阅者对象作为参数传入,就是subscriber对象。

那么createSignal方法中,[subscriber sendNext:@(1)]是什么意思呢?

看一下sendNext方法吧:

- (void)sendNext:(id)value {

@synchronized (self) {

void (^nextBlock)(id) = [self.next copy];

if (nextBlock == nil) return;

nextBlock(value); } }

我们可以发现,sendNext的实现,也就是直接执行上文中的nextBlock。也就是回答了上面第五个问题。

 总结一下,signal持有didSubscribe参数(createSignal传进来的那个block),subscriber持有nextBlock(就是subscribeNext传进来的那个block),当执行[signal subscribe:subscriber]的时候,signal的didSubscribe执行,内部有subscriber sendNext的调用,触发了subscriber的nextBlock的调用。到这里,我们基本把信号和订阅者,以及订阅过程分析完毕。

 第二部分 信号和事件###

刚才我们说过,signal有几个子类,每一个类型的signal订阅过程其实大同小异,而且初期常见的也就是RACDynamicSignal,其实我们不需要太关心这个问题,因为无论是自定义信号,还是框架定义的一些category,例如,textFiled的rac_textSignal属性,大多数都是RACDynamicSignal。另一个常见的类型RACSubject可以以后理解。

还有就是,我们刚刚谈到了三种事件,分别是next、error、complete,和分析next的订阅过程一样,举个例子,我们发送网络请求,希望在出错的时候,能给用户提示,那么首先,创建信号的时候,在网络请求失败的回调中,我们要[subscriber sendError:netError],也就是发送错误事件。然后在订阅错误事件,也就是subscriberError:...这样就完成了错误信号的订阅。

complete事件比较特殊,它有终止订阅关系的意味,我们先大致了解一下RACDispoable对象吧,我们知道,订阅关系需要有终止的时候,比如,在tableViewCell的复用的时候,cell会订阅model类产生一个信号,但是当cell被复用的时候,如果不把之前的订阅关系取消掉,就会出现同时订阅了2个model的情况。我们可以发现,subscribeNext、subscribeError、subscribeComplete事件返回的都是RACDisopable对象,当我们希望终止订阅的时候,调用[RACDisposable dispose]就可以了。complete也是这个原理。

 第三部分 进一步的深入###

RAC是一个非常庞大的框架,平时的一些教程会误导大家纠结flattenMap和map的区别,这些问题,让人找不到头绪,导致入门更加的困难。实际上,学习它需要一个循循渐进的过程,RAC有很多作用,解耦合,更高效的解决一类问题等等,总之,他是对常规的面向对象编程很好的补充。所以,在理解完订阅的过程之后,重要的是,投入实际的运用中,我观察了不少开源的项目,并结合自己的实践发现,其实flattenMap这样的操作,非常少,几乎没有,常用的无非就是以下几个:手动构建信号(createSignal)、订阅信号(subscribeNext)、使用框架的一些宏定义(RACObserve、RAC)、然后就是学习几个最简单的操作,例如map、merge等就可以开始了。如果希望深入研究,一定要把这些基础的东西吃透,然后在学习更多的操作,例如flattenMap,了解side effect和多播的概念,学会RACSubject的用法(这个也是非常重要的对象)等等。如果把这些操作比作武器的话,可能更重要的是内功,也就是理解他的思想,我们如何通过实战,知道恰当的利用他的强大,慢慢的熟悉和深入是水到渠成的事情。

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

推荐阅读更多精彩内容