有关RAC源码知识梳理
RACSignal :
1,创建信号
2,信号的订阅
3,发送信号
4,信号销毁
image.png image.png image.png image.png
下面每一步我们细细道来:
1、创建信号
创建信号类方法中传入了一个返回值是RACDisposable 类型,参数是遵守 RACSubscriber 协议的,名为 didSubscribe 的block,具体实现如下:
image.png
由上图可知,内部创建的是一个 RACDynamicSignal 类型的信号,并将 didSubscribe 传入,内部实现如下:
image.png
这里就是重点了,首先先创建了一个 RACDynamicSignal 类型的信号,然后将传入的名为 didSubscribe 的block保存在创建的信号的 didSubscribe 属性中,此时仅仅是保存并未触发。
总结:创建信号本质就是创建了一个 RACDynamicSignal 类型的信号,并将传入的代码块didSubscribe保存起来,待以后调用。
2、订阅信号
由上面的 信号类使用图可知,有三种订阅信号的方式,分别是订阅 Next 、Error 、 completed ,内部实现如下:
image.png
首先我们来看第一步,就是创建一个订阅者,并传入相应的block,创建实现如下:
image.png
订阅信号的实质是,创建一个订阅者,并保存相应的block,比如 net、error、或者complete,此时仅仅是保存并未触发!
由上面两图可知,三种订阅方式的流程模式是一致的,仅仅是保存的block不同而已,我们分析一种即可,所以 接下来就以 subscribeNext 为例来逐步分析。
我们看看执行订阅命令这块的实现,如下:
image.png
这里,首先我们要知道此处代码实现是在 RACDynamicSignal 里,图中的 didSubscribe 就是第一步创建信号中保存的 didSubscribe block。
由上图可知第一步创建信号中保存的 didSubscribe 代码块在这里执行,并传入了刚刚生成的订阅者(此处的订阅者中保存里 Next block代码块)。
此外,这里生成了一个 RACCompoundDisposable 类型的disposable,用来管理整个订阅结束及资源的清理,并以传入的订阅者、及当前信号、刚创建的disposable 生成一个 RACPassthroughSubscriber 类型的订阅者,此订阅者仅仅是将传入的三个对象整体包装了一下而已,实质起作用的还是在刚才创建的订阅者,所以,其包含的 next 代码块,依然直接调用即可。
之后,将执行 didSubscribe 代码块返回的 innerDisposable 传入刚刚生成disposable、并将执行此代码块的信号的 schedulingDisposable 也保存到RACCompoundDisposable 类型的disposable中,然后统一管理整个订阅结束及资源的清理。
其中的innerDisposable 就是 上面的 信号类使用图 中表示的第四步,其会在信号结束订阅的时候被调用,做一些清理资源的工作。
而 schedulingDisposable 的话,实际上就是执行代码块放到相应的调度器中,假如没有设置的话,即为 backgroundScheduler,并异步执行 didSubscribe 代码块,假如设置的话就会返回nil,并直接执行代码块,此处嵌套就有点深了,随后讲解取消订阅的时候再细说。
此处用到的RACCompoundDisposable 类型的disposable有点类似于可变数组 NSMutableArray 。而当 RACCompoundDisposable 对象被 disposed 时,它会调用其所包含的所有 disposable 对象的 -dispose 方法。所以可以做统一管理。
一句话总结:订阅信号本质就是创建了一个 RACPassthroughSubscriber 类型的订阅者,并将传入的代码块(三种类型的block)保存起来,待以后调用,同时调用了第一步创建信号中保存的代码块(didSubscribe),并传入创建的订阅者。
3、发送信号
由上面的 信号类使用图可知,发送同样对应着三种方式,处理如下:
image.png
这里有三点需要知道
第一,发送信号就是执行相应block,此处执行的就是第二步中保存的相应的block(next、error、complete);
第二,对于 sendError 和 sendCompleted 都是先取消订阅,再执行相应的代码块,而 sendNext 并未使订阅结束,这样的话,对之后讨论的各种组合方法中必须写上 sendCompleted来结束订阅的做法就好理解了;
第三,我们也看到三种方法中,假如信号没有相应的block代码块保存,即没有经过第二步去订阅保存代码块,就算是发送了信号也不会执行,此时也就是冷热信号的区别,当然用 RACSubject 来解释更容易理解;
一句话总结:发送信号就是执行订阅信号时对应的block。
4、取消订阅
经过上面三步,我们也了解到了想要结束订阅只要将相应生成的disposable执行dispose即可,那到底为什么呢?现在来讲一讲:
第二步订阅信号中有讲到,执行订阅的代码块实际上是放到相应的调度器中去执行的,如下图:
image.png image.png
如果不为nil,此时代码块就不会放到调度器里去执行,而是直接执行,此时disposable的dispose方法就没啥用了。
假如currentScheduler为nil的话,会默认一个调度器去管理代码块,具体实现如下:
image.png
如图所示,可知在代码块block未被调度的之前,生成的disposable被dispose的话,代码块就不会被执行了,如果没有被dispose,则放入销毁调度器继续执行销毁;
一句话总结:取消订阅就是把订阅信号获得的disposable信号 进行dispose处理,也就是可在调度器调度该代码块之前禁止调用。
2、RACSubject 的使用
RACSignal的原理搞清了,接下来就比较好理解了,毕竟信号类都是继承与RACSignal,作用不同也仅仅是部分实现的方式不同而已。
如下图:
image.png
1、创建信号
image.png由上图可知,创建RACSubject对象的时候同时创建了相应的一个disposable和一个订阅者数组,没有做其他事情。
2、订阅信号
大致流程与RACSignal是一致的,不同点在下面:
image.png
RACSinal订阅的时候直接执行了订阅命令,执行的是创建时保存的代码块,但RACSubject的做法是将订阅者保存到初始化时生成的那个订阅者数组内,此时每个订阅者都包含其对应的代码块(比如:next、error、complete)。
由此可以知道,RACSubject是可以多次订阅的,多次订阅就是把相应包含代码块的订阅者放入订阅者数组内。
另外,此处对于取消订阅做了一些清理工作,将保存的订阅者都移除。
一句话总结:订阅信号就是将代码块保存到订阅者中,并将订阅者添加到信号的订阅者数组中。
RACSinal订阅的时候直接执行了订阅命令,执行的是创建时保存的代码块,但RACSubject的做法是将订阅者保存到初始化时生成的那个订阅者数组内,此时每个订阅者都包含其对应的代码块(比如:next、error、complete)。
由此可以知道,RACSubject是可以多次订阅的,多次订阅就是把相应包含代码块的订阅者放入订阅者数组内。
另外,此处对于取消订阅做了一些清理工作,将保存的订阅者都移除。
一句话总结:订阅信号就是将代码块保存到订阅者中,并将订阅者添加到信号的订阅者数组中。
3、发送信号
大致流程与RACSignal是一致的,不同点在下面:
image.png
由上图可知,RACSubject的发送信号就是遍历自己的订阅者数组,然后分别发送信号。
这也是为什么如RACSubject有多个订阅者订阅,发送一个信号所有的订阅者都收到的原因。
一句话总结:发送信号就是遍历自己的订阅者数组,然后分别发送信号,并执行该订阅者保存的代码块。
3、其他信号类
还是那句话,信号类都是继承与RACSignal,作用不同也仅仅是部分实现的方式不同而已。
如不同的订阅信号实现:
image.png
不同的发送信号实现:
image.png
4、RACCommand 的使用
这个就比较复杂了,因为他是基于信号的一个对象,里面绑定了很多相关的信号,这里只是讲一讲他的执行原理,及常用的一些方法的分析。
先上图如下:
RACCommand的实现流程
1、创建命令
由上面 RACCommand流程图 可知创建的时候传入一个返回值为RACSignal类型,参数为id类型的名为input的一个block,具体实现如下:
image.png
由上图可知,command的创建实质是额外创建了一个名为 _activeExecutionSignals 的信号数组,并把传入的block保存为 signalBlock 属性。
一句话总结:创建命令就是创建了一个RACCommand类的对象,并将传入的block保存为 signalBlock 属性,然后初始化了一个信号数组,待以后接收命令信号。
2、订阅命令发出的信号
由上面 RACCommand流程图 可知订阅的信号经过了两步方法,第一步中executionSignals为创建命令时绑定的信号如下:
image.png
由上图可知,executionSignals实际上就是最新的第一步创建命令中创建的activeExecutionSignals信号数组。
第二步中的switchToLatest实质上取信号数组的最新的信号。
然后就订阅信号,和RACSignal的订阅流程一样,不再赘述。
一句话总结:订阅命令实质上就是订阅命令保存的信号数组中的最新的信号(目前代码的订阅)
3、判断命令是否执行
与executionSignals类似,在创建命令时也创建了一个信号来监听当前命令是否正在执行。
image.png
一句话总结:判断是否执行命令就是检测是否有活跃信号
4、执行命令
执行命令的话就是传入你所需要传入的对象即可,如下图
image.png
由上图,我们可以知道,执行命令先执行了第一步保存的signalBlock并传入接收到的参数,然后把得到的signal加入到第一步创建的 _activeExecutionSignals 信号数组中,待第二步订阅使用。
一句话总结:执行命令就是将传入的对象传入signalBlock生成signal,并将signal添加到_activeExecutionSignals 信号数组中。
二、关于常用操作方法
1、核心方法bind
在实际开发中我们并不需要直接调用bind方法,但我们所使用API都是以bind方法为基础的,想要了解操作方法的实现原理,bind我们不得不深究一下。
先上 bind 流程图如下:
image.png
由上图可知,bind 操作方法的流程如下:
1、创建信号源
2、绑定源信号,生成绑定信号
3、订阅绑定信号
4、发送源信号
5、取消订阅(不再赘述)
由此可知,与正常信号绑定订阅流程不同之处在于,多了一个绑定过程,并且最终订阅的是绑定生成的绑定信号。接下来我们一个一个理一下思路:
1、创建信号源
一句话总结:创建了一个 RACDynamicSignal 类型的信号,并将传入的代码块保存起来,待以后调用。
2、绑定源信号,生成绑定信号
由 bind操作流程图可知,bind 方法传入了一个 RACStream * (^RACStreamBindBlock)(id value, BOOL *stop) 的block代码块,其具体实现如下(只截取核心的代码):
image.png
由上图可知,bind操作实际上直接返回了一个绑定的信号,并将 didSubscriber 代码块 传入,保存到返回的信号内。
此外,绑定信号的 didSubscriber 代码块 中做了两件事
第一,将bind方法传入的block保存为bindingBlock,待以后用,并且初始化了一个信号数组;
第二,在代码块内部订阅了源信号,并做了一些处理,如下:
image.png
由上面可知,订阅源信号传入的block,被保存在相应订阅者中,待源信号发送信号触发,内部实现不再赘述。
一句话总结:bind操作实际上是直接生成绑定信号并返回,并且在生成绑定信号传入的didSubscriber block代码块中,保存了bind传入的block,初始化了信号数组,并且订阅了源信号,针对源信号发送信号的流程做了一些处理。(此时未执行,订阅才执行)
3、订阅绑定信号
订阅绑定信号就是实现了第二步中的 didSubscriber 代码块,就是说,第二步仅仅是保存,在订阅它的时候才会执行。
一句话总结:订阅绑定信号就是保存了nextBlock,并且创建订阅者,实现信号的didSubscriber block代码块。
4、发送信号
1、发送信号触发绑定信号 didSubscriber 代码块中订阅源信号时传入的NextBlock 代码块,如下:
image.png
2、是执行第三步中订阅绑定信号保存的从bind传入的block代码块,并传入由订阅时的传参生成的新的传参,生成returnSignal,具体实现如下
image.png
由代码可知,returnSignal实际上是保存了传入的新值,此时就是真正意义上的将源输入做了改变,最后实际输出的就是新保存的值。
4、接下来就是将生成的returnSignal做 addSignal 操作如下:
image.png
由上图代码可知,内部实现是直接订阅了传入的returnSignal。
5、之前我们也说过,不同信号对应不同的订阅代码,所以我们来看看returnSignal的订阅时的代码,如下:
image.png
由上图代码可知,returnSignal在订阅的时候就同时做了发送信号的任务,即直接就会走订阅保存的代码块,如下:
image.png
此处触发的是绑定信号的订阅者发送信号,就是执行绑定信号的 nextBlock,即: image.png
运行结果:
image.png
一句话总结:bind操作方法实质上就是生成新的绑定信号,利用returnSignal作为中间信号来改变源数据生成新的数据并执行新绑定信号的nextBlock代码块!
2、映射方法 map
所有操作方法都是以bind方法为核心的,这里分析一下映射方法即可,不在赘述其他方法,原理都是一样的,就是看怎么组合,怎么去处理相关逻辑达到不同操作方法达成的效果!
首先,看一下map的操作流程图,如下:
image.png
很显然,与 bind 的区别就是生成绑定信号的过程不同,具体实现如下:
image.png
由上图可知,从外面传入了一个参数为value,返回值为id类型的block代码块,实际返回的是 flattenMap,操作方法生成的信号,具体实现如下:
image.png
看,就是这里,在 map 方法中,返回的是 flattenMap 方法生成的信号,而 flattenMap 内部返回的是 bind 方法返回的信号,又回到了第一部分讲解的 bind实现原理
上图中的 stream,就是returnSignal,即 flattenMap方法传入的 block(value),如下图:
image.png具体生成ReturnSignal的方法,还得往回看 map 方法如下:
image.png
由上图可知,传入的value已经被 map保存的方法所映射(即改变),如下:
image.png
一句话总结:map 映射方法的实质就是 bind 方法的深度封装,实际的信号流的流程还是以 bind为核心,只是巧妙的做了一些逻辑处理而已。
3、其他操作方法
对于操作方法,核心就是 bind,各种作用的不同就是各种 signal与 bind 方法的结合,外加一些巧妙的逻辑处理,明白了信号的原理、绑定的原理,任何其他的组合或者运用都是万变不离其宗的。