上篇博客我们聊完SignalProducer结构体的基本实现后,我们接下来就聊一下SignalProducerProtocol延展中的start和lift系列方法。SignalProducer结构体的方法扩展与Signal的扩展相同,都是面向协议的扩展。首先创建了一个SignalProducerProtocol协议,使SignalProducer在延展中遵循SignalProducerProtocol协议。然后我们再对SignalProducerProtocol进行扩展。这样一来,SignalProducer结构体就拥有了我们在SignalProducerProtocol协议中扩展的方法了。这也是我们之前所说的“面向协议的扩展”。
今天我们就来聊一下SignalProducerProtocol协议扩展中的start和lift系列方法。无论是start系列方法还是lift系列方法,都是在SignalProducer的startWithSignal(setup)核心方法的基础上构建的。而关于startWithSignal(setup)方法的具体实现,上篇博客给出了相应的介绍并给出了该核心方法的使用方式,在此就不做过多赘述了。
而在SignalProducerProtocol协议扩展中的方法,基本上全是对b方法的封装,只不过使用场景和功能更为专一,用法更为方便。接下来我们就来看一下SignalProducerProtocol协议扩展的start和lift系列方法。
一、Start系列方法
在SignalProducerProtocol协议扩展中的Start系列方法的主要作用是往SignalProducer中的signal的Bag中添加观察者的,这一点与Signal的observer()系列方法类似。下方就是start系列的部分方法,在下列的方法中,核心的是start(observer)方法。该方法的参数是一个观察者的对象,start(observer)方法就负责将该观察者添加到SignalProducer中的signal的Bag。而下方一系列的start方法都是调用的start(observer)方法。
因为Start系列方法极为相似,在此就不一一进行列举了,下方是部分start方法,其余省略的与下方代码的实现原理一致,在此就不做过多赘述了。
看完start系列方法的实现,我们再来看一下start系列方法使用方式。下方是start系列方法的部分使用案例,具体介绍如下:
下方的控制台中是该代码段实例的输出结果,从输出结果我们我们知道,start()系列方法的主要作用是往signal中添加观察者的。观察者添加完毕后,就调用SignalProducer构造函数的尾随闭包。具体代码如下所示。
二、闭包类型的高级用法
在聊Lift之前呢,我们先来看一下闭包类型使用的示例。因为Lift相关方法的实现较复杂一些,其中涉及闭包方法类型的一些高级用法。接下来我们从Swift语言的角度来看一下函数类型的高级用法,该用法也是在Lift中使用到的,还是有必要单独的拎出来聊一下的。下方是一些具体的示例。
1、创建MyClass类
首先我们创建一个简单的类MyClass,该类比较简单,就是一个属性、一个构造器、一个add方法。add(other)方法是MyClass的主角稍后我们会用到。因为下方的代码比较简单在此就不做过多赘述了。MyClass下方紧跟着的就是MyClass类测试用例,如下所示。
2、创建MyClassProducer类
接下来我们创建一个MyClassProducer类,在该类中使用到了MyClass类。在MyClassProducer中也有一个add方法,只不过该add方法接收的是一个闭包参数,而返回值是一个MyClass类型。add()的参数类型为(MyClass) -> (MyClass) -> MyClass。该闭包类型接收一个MyClass类型的参数,然后返回一个(MyClass) -> MyClass类型的闭包。而(MyClass) -> MyClass类型的闭包的参数又是一个MyClass类型,而返回值是MyClass。
在add(closure)方法中直接执行了closure闭包,并将闭包最终返回的MyClass类型的对象进行返回。具体代码如下所示。
3、MyClassProducer类的非常规用法
接下来我们就来看一下MyClassProducer类中add(closure)方法直接的使用方式。当然,在正常情况下,上述写法尤为繁琐,而是使用方式也是比较麻烦的。下方我们就来看一下直接调用add(closure)方法的代码。
4、MyClassProducer类中add方法的常规用法
上一部分中add(closure)方法的使用方式是非常规用法,因为直接使用add(closure)方法显得晦涩难懂,而且闭包嵌套闭包,闭包返回闭包的形式着实让人费解。可以说直接使用没有什么好处。接下来我们就来看一下add(closure)的常规使用方式。
下方代码片段是add(closure)反复的常规使用方式。从下方代码片段中我们可以直接看出,add(closure)接收的不在是一个闭包,而是MyClass.add(other:)的类型。其实就是把MyClass.add(other:)这个类型所对应的方法体传给了add(closure)闭包。也就是说add(other:)方法的类型与(MyClass) -> (MyClass) -> MyClass闭包类型是等价的。所以可以将add(other:)方法的方法体提供给add(closure)作为参数。换句话说(MyClass) -> (MyClass) -> MyClass类型等价于MyClass.add(MyClass)->MyClass。
这样做的好处就是可以让数据与算法进行分离,add(closure)参数闭包对应什么样的算法那么add(closure)就执行什么样的算法。这一点在SignalProducer类中的Lift系列方法中表现的淋漓尽致。稍后我们会介绍到。
三、Lift系列的核心方法实现
接下来我们就来看一下SignalProducer中的Lift系列方法的代码实现。当然,因为Lift系列方法比较多,下方会给出Lift系列方法中比较核心的内容,而剩下的未讲解的则是从这些核心方法中延伸出来的方法。接下来我们就由易到难,来看一下Lift系列方法的代码实现。
1、lift<U, F>(transform)代码实现
该方法算Lift系列中比较独立而且比较核心的方法了。下方代码片段就是该方法的实现。解释如下:
为了更进一步来了解上述代码的实现方式以及运行方式,我们还需结合示例进行分析。下方代码片段就是上述方法的使用示例,介绍如下:
根据lift(transform)的代码实现以及上述示例的运行结果,我们给出了下方的原理图。下方这个简图就是上述示例执行的整个过程,一图胜千言。根据下图结合上述示例应该是一目了然的。在此就不在过多赘述了。
在SignalProducer的延展中,下方的方法全是在上述lift(transform)的基础上实现起来的,归根结底使用的还是Signal中相应的方法。下方这些方法的工作方式以及运行原理和上面这个图非常相似。只不过是生成中间的信号量的方式不同。
下方代码片段中每个方法在使用lift(transform)方法时使用了尾随闭包的简写形式。其中的$0参数就是尾随闭包的Signal参数,$0信号量通过调用其对应的方法生成的新的信号量就是该尾随闭包的返回值。具体如下所示。当然下方只是部分使用lift(transform)的方法,其他的与下方类似,就不做过多赘述了。
2、liftRight<U, F, V, G>(transform)代码实现
在看liftRight方法的代码实现之前呢,还是需要回顾一下本篇博客的第二部分闭包类型高级用法的内容的。因为本篇博客第二部分中的内容以及使用示例有助于理解liftRight方法的使用方式以及运行模式。
下方代码片段就是liftRight方法的具体实现,我们需要注意的是liftRight方法是private类型的,也就是说该方法不对用户直接暴漏,用户不可以直接调用该方法。当然,此刻我们需要看liftRight方法的代码实现,要给出相应的使用示例,所以我们可以将private改成public。
从下方代码实现中,我们可以直观的感受到liftRight方法的代码实现是比较复杂的。其复杂就复杂在liftRight的入参和返回值都是比较复杂的。首先我们来看一下liftRight参数。其参数是一个名为transform的闭包,该闭包的类型为(Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>,该闭包类型需要一个Signal<Value, Error>类型的参数,其返回值是一个类型的(Signal<U, F>) -> Signal<V, G>闭包。
从该闭包类型,然后在参考第二部分中的(MyClass)->(MyClass)->MyClass闭包类型,以及该闭包类型与MyClass.add(MyClass)->MyClass方法的对应关系。我们不难看出(Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>类型的闭包等价于Signal<Value, Error>.method(Signal<U, F>)->Signal<V, G>类型的方法。而在Signal类中有好多符合Signal<Value, Error>.method(Signal<U, F>)->Signal<V, G>类型的方法,如Signal中的combineLatest、withLatest、take(until:)、skip(until:)等方法,也就是说这些方法的方法体都可以作为transform闭包的闭包体,稍后我们会进行介绍。
3、liftRight<U, F, V, G>(transform)直接调用
按照老规矩,我们先给出liftRight方法的使用方式,当然此处是liftRight方法的使用方式是非常规的做法,因为我们是直接拿过来用的。不过这样做更有利于我们理解liftRight的代码结构和运行方式。
下方代码片段就是我们直接调用liftRight方法的示例,介绍如下:
针对上述代码的执行过程,还是来张图来的直接。下方这张简图就是上述代码的执行过程。执行过程,与上述代码的执行步骤是一一对应的。可以根据代码的运行步骤后下方的简图进行比较。关于下方简图,就不做过多赘述了。
4、liftRight<U, F, V, G>(transform)常规使用方式
上面一小节我们直接调用了liftRight方法,接下来我们就来看一下liftRight的常规使用方式。所谓的常规使用方式是使用已经实现过的方法的方法体作为transform的闭包体。上面列举了一些Signal中的方法类型与transform的闭包类型等价的方法其中就有Signal.combineLatest(Signal)->Signal方法。接下来我们就使用combineLatest的方法体来替换上述liftRight方法的尾随闭包。
下方红框中就是我们替换的内容,其他代码不变。我们发现替换后,输出结果与我们之前一致。下方的这种使用方式才是liftRight方法正确的使用姿势。
看完上述liftRight的实现以及使用方式,接下来我们就来看一下SignalProducer内部是如何使用liftRight方法的。下方随便找了一个liftRight的使用方式,举一反三。
四、liftRight方法与liftLeft方法对比
而在Lift系列方法中,使用liftRight()方法的方式就是上述代码段的方式。稍后我们会一一介绍。上述这种技巧使用起来还是比较方便的。出来liftRight方法,还有一个liftLeft()方法。liftLeft()方法的实现方式与liftRight()方法代码实现即为相似,只是producer的startWithSignal()方法的调用顺序不同。接下来我们就来看看这两者的不同之处。
下方代码片段就是liftRight以及liftLeft方法的代码实现。经过对比我们不难发现两者的主要区别是otherProducer和self的startWithSignal()方法的执行顺序不同。在liftRight()方法中otherProducer的startWithSignal会先执行完毕,而self的startWithSignal()会后执行完毕。而liftLeft恰好于此相反。
我们以producer.liftRight()(otherProducer)为例,这个Right指右边的otherProducer的startWithSignal()方法率先执行完毕。而producer.liftLeft()(otherProducer)则指左边的producer的startWithSignal()方法率先执行完毕。
为了更直观的感受上述两个方法的不同之处,特此给出了下方的示例。根据下方示例的输出结果,liftRight与liftLeft的区别一目了然。对下方示例的介绍如下:
根据上述代码片段的输出结果,我们不难看出在producer.liftRight()(otherProducer)中右边的otherProducer的startWithSignal()方法率先执行完毕。而producer.liftLeft()(otherProducer)则指左边的producer的startWithSignal()方法率先执行完毕。
下方是liftLeft()方法是使用方式,与liftRight用法是一致的,如下所示:
在SignalProducer的好多方法中都是在lift、liftRight或者liftLeft方法的基础上实现的特定功能。以后的博客会陆陆续续的介绍到。因篇幅有限,今天的博客就先到这儿,下篇博客我们会继续解析ReactiveSwift框架中的其他内容。
上述代码github分享地址:https://github.com/lizelu/TipSwiftForRac