图解 RACCommand 底层实现原理

uouopp 4年前
   <p>RACCommand作为RAC框架里比较特殊的存在,继承于NSObject,它是一个类而不是信号。整个类的结构也比较简洁,根据官方的解释,可以使用它来创建和订阅相应事件的信号。对于副作用相关的操作,可以带来极大的方便。RACCommand类本身主要可以分为5个属性3个方法。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fb4a242829103b6a3d082378877e0f81.jpg"></p>    <h2>初始化</h2>    <p>接下来对于图中罗列的方法和属性做一个分析。</p>    <pre>  <code class="language-objectivec">- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;  - - (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;</code></pre>    <p>这两个方法可以放在一起分析, initWithSignalBlock 其实就是调用 initWithEnabled ,传入的enableSignal为空,即默认信号可执行。在源码里有说明</p>    <pre>  <code class="language-objectivec">if (enabledSignal == nil) {        enabledSignal = [RACSignal return:@YES];  } else {      enabledSignal = [[[enabledSignal                         startWith:@YES]                         takeUntil:self.rac_willDeallocSignal]                         replayLast];  }</code></pre>    <p>RACCommand的初始化,实现代码很长,通常来说,分成4部分来分析。为了更清晰地理解,参照着源码和其他分析文章,画了这张图</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/70f11be47418bcc3a32fda2390946016.jpg"></p>    <p>图中红色箭头标注了初始化的顺序。</p>    <p>对于executionSignals进行初始化。</p>    <p>_activeExecutionSignals 是一个数组,在初始化方法里给他重新定义为可变数组。当数组里有新的信号加入的时候,会将新信号包装成元祖。通过一些处理后得到二阶热信号 newActiveExecutionSignals ,因为是二阶,所以里面还包含着其他信号。在第一步里,对 newActiveExecutionSignals 进行了过滤,只留下非错误信号,即得到RACCommand的属性之一 executionSignals ,表示包含信号的信号。</p>    <p>对于errors进行初始化</p>    <p>对第一步里生成的 newActiveExecutionSignals ,进行另一种操作,先用flattenMap操作,将其降为一阶信号,然后转化为RACCommand的属性之一 errors 。</p>    <p>RACCommand会将错误信号都装到它自己的errors信号中,看到这单词复数形式,应该就能想到里面可以包含很多错误。</p>    <p>对于executing进行初始化</p>    <p>属性 executing 表示是否有信号正在执行。对于一开始的 _activeExecutionSignals ,使用宏RACObserve进行监听,一旦出现变化,那么会生成一个包装BOOL的信号 immediateExecuting ,它的初始值被设置为NO,一旦发生了改变,就会发送信号。取其最新值给 executing ,这即是属性 executing 的初始化过程。</p>    <p>对于enabled进行初始化</p>    <p>监听 allowsConcurrentExecution ,默认值是NO,如果变化了,返回一个包装了YES的信号给 moreExecutionAllowed 。如果没有变化,判断immediateExecuting,对它进行取非运算,得到的BOOL包装成信号给 moreExecutionAllowed 。 moreExecutionAllowed 表示是否执行下一个信号,这里将它和enableSignal进行了如图中的运算。enableSignal是方法初始化时传入的参数,它的具体内容就是前面提到的这段代码</p>    <pre>  <code class="language-objectivec">if (enabledSignal == nil) {        enabledSignal = [RACSignal return:@YES];  } else {      enabledSignal = [[[enabledSignal                         startWith:@YES]                         takeUntil:self.rac_willDeallocSignal]                         replayLast];  }</code></pre>    <p>当enableSignal为nil的时候,就会传YES给它。如果不是nil,也给它一个初始值YES,防止空信号比如[RACSignal empty],[RACSignal never]。</p>    <p>二者运算之后得到了immediateEnabled,从单词字面理解,算是一个短暂的变量,连接这些信号,当变化时取最新值发送,来得到最后的 enabled 属性。</p>    <p>总结来说</p>    <p>RACCommand初始化,就是对它的各项属性进行了初始化。signal在初始化方法里只是通过Block传入,并没有做其他操作。传入的信号,它的作用体现在接下来介绍的 execute 方法里。</p>    <h2>execute</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b1d7afb2c0aec74f062ae565eed929e9.jpg"></p>    <p>根据execute的实现源码,在一开始的时候会先判断是否可执行,如果不能,则直接返回错误信号。如果可以执行,那么开始执行这一系列动作。从SginalBlock里获得信号,将其转换为执行信号,这时候的信号是一个冷信号,通过malticast操作,将其转换为热信号,添加到activeExecutionSignals数组中。然后实现信号的订阅,最后返回,完成整个RACCommand的执行过程。</p>    <h2>rac_command</h2>    <p>RACCommand一个常见的应用是将按钮点击事件与RACCommand绑定。在不看源码的情况下,从外部理解,应该是类似于以下的逻辑</p>    <pre>  <code class="language-objectivec">[button addTarget:id action:@selector(click) forControlEvents:UIControlEventTouchUpInside];    - (void)click{    [Command execute:id]  }</code></pre>    <p>RAC框架里给UIButton添加了 rac_command 的属性。实现的代码比较简洁,涉及了runtime函数里的对象关联,下面依然通过一张图来解释它实现的过程。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/39bd31bcb656ee86154bc19046c6bd19.jpg"></p>    <p>关联的目的在于使得UIButton的target = self,action = @selector(rac_commandPerformAction:)</p>    <h2>参考</h2>    <p>主要是参考了 <a href="/misc/goto?guid=4959739793209976624" rel="nofollow,noindex">ReactiveCocoa 中 RACCommand底层实现分析</a> 来帮助理清关系,画出以上关系图。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/c7d7169e462a</p>    <p> </p>