上篇博客我们聊完SignalProducer结构体的基本实现后,我们接下来就聊一下SignalProducerProtocol延展中的start和lift系列方法。SignalProducer结构体的方法扩展与Signal的扩展相同,都是面向协议的扩展。首先创建了一个SignalProducerProtocol协议,使SignalProducer在延展中遵循SignalProducerProtocol协议。然后我们再对SignalProducerProtocol进行扩展。这样一来,SignalProducer结构体就拥有了我们在SignalProducerProtocol协议中扩展的方法了。这也是我们之前所说的“面向协议的扩展”。

今天我们就来聊一下SignalProducerProtocol协议扩展中的start和lift系列方法。无论是start系列方法还是lift系列方法,都是在SignalProducerstartWithSignal(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系列方法的部分使用案例,具体介绍如下:

  • 首先通过SignalProducer的init(value)构造器创建一个producer对象,备用。

  • 然后再创建一个观察者subscriber1,并给出Value事件的处理闭包。

  • 然后调用start(observer)和startWithSignal()方法将subscriber1添加到信号量中。

  • 之后再调用一些列start()方法往signal中添加新的信号量。

下方的控制台中是该代码段实例的输出结果,从输出结果我们我们知道,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)方法的代码。

  • 首先我们创建了一个MyClassProducer类型的对象myProducer1。

  • 然后直接调用myProducer1的add(closure)方法。add()的尾随闭包的参数是MyClass类型的对象myclass1,其返回值是(MyClass)->MyClass类型的闭包,所以我们就直接在尾随闭包块中返回了一个闭包块,该返回的闭包块的类型就是(MyClass)->MyClass。然后在(MyClass)->MyClass类型的闭包块中返回了一个MyClass类型的对象,在创建该对象时,使用到了上述两个闭包的参数myclass1和myclass2。

  • 然后我们将sum01对象的des属性进行打印,就是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系列中比较独立而且比较核心的方法了。下方代码片段就是该方法的实现。解释如下:

  • 该方法是一个泛型方法,可以容纳两个泛型<U, F>。其方法参数是一个逃逸闭包transform,而这个闭包的参数是一个类型为Signal<Value, Error>的信号量,而返回值是类型Signal<U, F>的信号量,也就是说transform闭包的功能负责将Signal<Value, Error>类型的信号量经过某些算法转换成Signal<U, F>类型的信号量。而整个函数的返回值是一个SignalProducer<U, F>类型的信号量生产者。

  • 方法体中,返回了一个新的SignalProducer对象,在SignalProducer构造器的尾随闭包中调用了原SignalProducer对象的startWithSignal()方法。在startWithSignal()方法的尾随闭包中将原SignalProducer对象的信号量signal经过transform闭包转换成一个新的信号量后,将新SignalProducer对象的observer添加到这个转换后的信号量的Bag中,成为其观察者。具体代码如下所示。

  

为了更进一步来了解上述代码的实现方式以及运行方式,我们还需结合示例进行分析。下方代码片段就是上述方法的使用示例,介绍如下:

  • 首先创建了一个类型为SignalProducer<Int, NoError>的对象producer。在该对象的尾随闭包中,发送了一个Value事件,该事件的值为整数8888。

  • 然后通过producer对象的lift方法创建了一个新的对象liftProducer。在lift方法的尾随闭包中将producer对象内部的Signal<Int, NoError>类型的signal信号量通过信号量的map方法将其转换成Signal<String, NoError>类型的信号量,并返回。有下方代码以及lift()方法的实现容易知道,因为liftProducer对象中的Observer对象被添加到转换后的Signal<String, NoError>类型的信号量中作为观察者,所以liftProducer对象的类型是SignalProducer<String, NoError>。

  • 下方代码中lift()方法的尾随闭包就是上述函数实现中的transform的闭包体。下方的signal参数就是transform在调用时传入的参数。

  • 接着,我们有创建了三个类型为Observer<String, NoError>类型的观察者,然后将这些观察者都添加进行liftProducer对象的信号量中。然后我们会看到控制台上打印的观察消息。

  

根据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方法的示例,介绍如下:

  • 首先我们通过常规方法创建了一个类型为SignalProducer<String, NoError>的对象producer。

  • 然后根据liftRight方法的代码实现,创建了两个类型别名。LiftRightProducerClosureType类型就是producer对象调用liftRight方法是所返回的闭包类型,ClosureReturnType则是liftRight方法的参数的闭包所返回的闭包类型。

  • 类型定义好后,就该让producer对象调用liftRight方法了。而下方的liftRightProducerClosure(SignalProducer)则是该方法返回的闭包常量。在该liftRight的返回闭包中,我们将producer对象所对应的信号量signal以及liftRightProducerClosure闭包所接收的SignalProducer对象中的信号量otherSignal,调用了combineLatest方法进行了合并,具体做法如下。

  • 然后又创建了一个strProducer对象,并为其绑定了一个signal信号量。然后执行liftRightProducerClosure(strProducer),该闭包会返回一个新的otherProducer对象,紧接着执行otherProducer的startWithValues()方法。然后调用strProducer所绑定信号量的Observer发送值。具体结果如下所示:

针对上述代码的执行过程,还是来张图来的直接。下方这张简图就是上述代码的执行过程。执行过程,与上述代码的执行步骤是一一对应的。可以根据代码的运行步骤后下方的简图进行比较。关于下方简图,就不做过多赘述了。

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的区别一目了然。对下方示例的介绍如下:

  • 首先我们创建了两个SignalProducer的对象,一个发送0,1,2的值,另一个发送A、B、C的值。

  • 然后让producer1对象调用liftRight方法, 使用liftRightProducerClosure来暂存返回的闭包,将producer2传入闭包中。然后调用rightProducer的startWithSignal方法。以类似的步骤调用lifeLeft方法

  

根据上述代码片段的输出结果,我们不难看出在producer.liftRight()(otherProducer)中右边的otherProducer的startWithSignal()方法率先执行完毕。而producer.liftLeft()(otherProducer)则指左边的producer的startWithSignal()方法率先执行完毕。

下方是liftLeft()方法是使用方式,与liftRight用法是一致的,如下所示:

在SignalProducer的好多方法中都是在lift、liftRight或者liftLeft方法的基础上实现的特定功能。以后的博客会陆陆续续的介绍到。因篇幅有限,今天的博客就先到这儿,下篇博客我们会继续解析ReactiveSwift框架中的其他内容。

上述代码github分享地址:https://github.com/lizelu/TipSwiftForRac

ReactiveSwift源码解析(九) SignalProducerProtocol延展中的Start、Lift系列方法的代码实现的更多相关文章

  1. ReactiveSwift源码解析(五) SignalProtocol的observe()、Map、Filter延展实现

    上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...

  2. ReactiveSwift源码解析(三) Signal代码的基本实现

    上篇博客我们详细的聊了ReactiveSwift源码中的Bag容器,详情请参见<ReactiveSwift源码解析之Bag容器>.本篇博客我们就来聊一下信号量,也就是Signal的的几种状 ...

  3. ReactiveSwift源码解析(一) Event与Observer代码实现

    ReactiveCocoa这个框架是做什么用的本篇博客就不做过多赘述了,什么是"响应式编程"也不多聊了,自行Google吧.本篇博客的主题是解析ReactiveCocoa框架中的核 ...

  4. ReactiveSwift源码解析(二) Bag容器的代码实现

    今天博客我接着上篇博客的内容来,上篇博客我们详细的看了ReactiveSwift中的Observer已经Event的代码实现.接下来我们来看一下ReactiveSwift中的结构体Bag的实现.Bag ...

  5. ReactiveSwift源码解析(六) SignalProtocol的take(first)与collect()延展实现

    上篇博客我们聊了observe().map().filter()延展函数的具体实现方式以及使用方式.我们在之前的博客中已经聊过,Signal的主要功能是位于SignalProtocol的协议延展中的, ...

  6. ReactiveSwift源码解析(八) SignalProducer的代码的基本实现

    在前面几篇博客中我们详细的聊了ReactiveSwift中的Bag.Event.Observer以及Signal的使用方式和代码实现.那么在接下来的这几篇博客中,我们就依附于之前博客的基础上来聊一聊S ...

  7. ReactiveSwift源码解析(十) Lifetime代码实现

    为了之后博客的进行,本篇博客我们就来聊一下ReactiveSwift框架中的Lifetime类的具体实现.从Lifetime这个名字中我们就这道,就是生命周期.在ReactiveSwift中使用Lif ...

  8. ReactiveSwift源码解析(十二) MutableProperty基本代码实现

    前两篇博客我们分别聊了ReactiveSwift框架中的负责标记对象的生命周期的类Lifetime以及负责原子性操作的Atomic类的具体代码实现.前两篇博客之所以聊Lifetime以及Atomic的 ...

  9. ReactiveSwift源码解析(七) Signal的CombineLatest的代码实现

    本篇博客我们就来聊一下combineLatest()的使用以及具体的实现方式.在之前的<iOS开发之ReactiveCocoa下的MVVM>的博客中我们已经聊过combineLatest( ...

随机推荐

  1. 扔掉log4j、log4j2,自己动手实现一个多功能日志记录框架,包含文件,数据库日志写入,实测5W+/秒日志文件写入,2W+/秒数据库日志写入,虽然它现在还没有logback那么强大

    讲到log4j,现在国外基本是没有开发者用这个框架了,原因大致有几点,1.功能太少:2.效率低下:3.线程锁bug等等等各种莫名其妙的bug一直都没解决. 其实最重要的是log4j的作者自己也放弃了l ...

  2. 渗透测试工具Nmap从初级到高级

    Nmap是一款网络扫描和主机检测的非常有用的工具. Nmap是不局限于仅仅收集信息和枚举,同时可以用来作为一个漏洞探测器或安全扫描器.它可以适用于winodws,linux,mac等操作系统.Nmap ...

  3. iOS gcd 串行,并行,同步,异步代码研究

    参考文章: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #527eff } span.s1 { } http: ...

  4. 解决Ubuntu开关机动画不正常方法

    联想的笔记本,显卡NVIDIA GT218M,默认使用开源的驱动,但挂起后,再唤醒就黑屏回不到桌面. 1.解决办法:安装NVIDIA专有驱动 $sudo apt-get install nvidia- ...

  5. Docker Daemon 参数最佳实践

    1. Docker Daemon 配置参数 限制容器之间网络通信 在同一台主机上若不限制容器之间通信,容器之间就会暴露些隐私的信息,所以推荐关闭 docker daemon –icc=false 使用 ...

  6. Java线程池入门必备

    线程池 一. 线程池的简介 1.什么是线程池?   最早期的工作线程处理任务的模型.一个任务的到来,会伴随着线程的创建,当处理完任务后,线程会被销毁,资源回收.这种一个任务一个线程一系列创建销毁的模式 ...

  7. a链接返回上一页

    <a href="javascript:void(0);" onclick="javascript:history.go(-1);" style='mar ...

  8. C/C++中的volatile究竟是什么鬼?

    将变量或对象声明为volatile类型后,每次对变量的访问都是从其内存直接读取.那什么时候对变量的访问不是从其内存读取的呢?一种常见的情况就是编译器开启了优化选项,这时候对变量的访问有可能就是从寄存器 ...

  9. workday1

    前天是实习的第一天,现在补下感想 总的来说还是不错的,师兄很nice,师妹很羞涩,我很尴尬,我的交际能力还是有待提高(主要是普通话不标准~~~~(>_<)~~~~) 早上由华工C12穿梭到 ...

  10. java--while、do while、for三种循环体

    1.for可以记录执行次数: 2.while.do while的i放在sum的后面和for得到的执行次数和结果是一致的. 1.从执行结果来看,放在前面,虽然执行次数和i放在sum的后面是相同,但是结果 ...