ReactiveSwift源码解析(九) SignalProducerProtocol延展中的Start、Lift系列方法的代码实现

上篇博客我们聊完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

时间: 2024-08-04 09:37:54

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

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

ReactiveCocoa这个框架是做什么用的本篇博客就不做过多赘述了,什么是"响应式编程"也不多聊了,自行Google吧.本篇博客的主题是解析ReactiveCocoa框架中的核心模块ReactiveSwift中的两个核心类的实现,也就是对Event和Observer这两个类进行解析.之所以把这两个类放在一块聊,是因为这两个类比较独立,可以说是ReactiveSwift中的两个原子类.Event确切的说是一个枚举,其中有几种事件,而Observer类的对象就是这些事件的发送者.所以把

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

为了之后博客的进行,本篇博客我们就来聊一下ReactiveSwift框架中的Lifetime类的具体实现.从Lifetime这个名字中我们就这道,就是生命周期.在ReactiveSwift中使用Lifetime来标记一个对象的生命周期,其实主要功能还是将对象的deinit()析构函数通过发送信号量将其回调出来.接下来我们就来看一下Lifetime类的实现.Lifetime类与Event和Observer相似,也是比较原子性的类,以原子组件的形式存在于ReactiveSwift中. 下方我们会先给

ReactiveSwift源码解析(十一) Atomic的代码实现以及其中的Defer延迟、Posix互斥锁、递归锁

本篇博客我们来聊一下ReactiveSwift中的原子性操作,在此内容上我们简单的聊一下Posix互斥锁以及递归锁的概念以及使用场景.然后再聊一下Atomic的代码实现.Atomic主要负责多线程下的原子操作,负责共享资源的同步一致性.而在Atomic中就是使用到了Posix互斥锁和递归锁.在聊上述内容之前,我们先来回顾一下Swift语言中延迟执行defer的使用方式,在之前Swift编程的相关博客中也涉及到了defer的使用方式.defer因为Atomic使用到了延迟操作,所以下方我们再做一个

Java集合干货系列-(一)ArrayList源码解析

前言 今天来介绍下ArrayList,在集合框架整体框架一章中,我们介绍了List接口,ArrayList继承了AbstractList,实现了List.ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的.构造图如下:蓝色线条:继承绿色线条:接口实现 正文 ArrayList简介 ArrayList定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomA

HashSet源码解析

今天来介绍下HashSet.前面,我们已经系统的对List和Map进行了学习.接下来,我们开始可以学习Set.相信经过Map的了解之后,学习Set会容易很多.毕竟,Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的). 构造图如下: 蓝色线条:继承 绿色线条:接口实现 正文 对于HashSet而言,它是基于HashMap来实现的,底层采用HashMap来保存元素.所以如果对HashMap比较熟悉,那么HashSet是so easy!! HashSet简介 HashSe

多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面大致的了解了Thread的一些方法和属性下面对一些方法进行运用看看具体效果<下面可能还是会贴很多的源代码,其实我是拒绝的,我只想贴每个方法的代码,但是有时候看到一个方法里面有调用了方法,但是笔者有没有给出来,很蛋疼,有种爽到一半的感觉,所以我还是会把它贴出来,希望一次就能挖到底,不论有没有全懂,但至

Android Monkey源码解析

一.使用 Monkey的使用很简单,需要注意的是各个参数的意义要搞清楚. 这篇文章并不会讲其使用,具体可以参见Google的官方文档[1],或者一篇博客[2]. 二.源码解析 1 ,参考同事和前辈的意见,阅读代码首先得理清楚主线,也即是执行流程.对应到Monkey中,也就是怎么通过在控制台中输入一串命令,就可以得到相应的测试结果的.因为主类Monkey中有main方法存在,一路跟下去,便可以理清楚其主线.下图是自己画的一个UML顺序图. 2,对于MonkeyEventSource,主要是Monk

Netty5源码解析

Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel

YOLOv3目标检测:原理与Darknet源码解析

Linux创始人Linus Torvalds有一句名言:Talk is cheap. Show me the code. (冗谈不够,放码过来!). 代码阅读是从入门到提高的必由之路.尤其对深度学习,许多框架隐藏了神经网络底层的实现,只能在上层调包使用,对其内部原理很难认识清晰,不利于进一步优化和创新. YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长. YOLOv3的实现Darknet是使用C语言开发的轻型开源深度学习框架,依赖少,可移植性好,可以作为很好的代码阅读案例,让