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

当然确切的说,本篇博客是对 ReactiveSwift框架 的部分解析,而ReactiveCocoa这个框架又是在ReactiveSwift框架的基础上搭建起来的,所以我们先来看一下ReactiveSwift这个框架中的代码实现。当然,我们之前发表过ReactiveCocoa的相关博文,如《iOS开发之ReactiveCocoa下的MVVM》,该篇博客的主题还是ReactiveCocoa框架的应用,而本篇博客或者说ReactiveCocoa源码解析系列博客是对ReactiveCocoa框架实现的深度解析。当然这种深度解析有一部分是Swift语言层面的,因为ReactiveCocoa框架中有好多Swift语言的高级用法,当然还有一些架构层面的,通过源码实现,我们要分析出这样设计的好处以及优点。

抛去“响应式编程”的概念,ReactiveCocoa的本质还是对“观察者模式”的使用,关于观察者模式,请参考之前的博客《设计模式(二):自己动手使用“观察者模式”实现通知机制》。也可以说ReactiveCocoa是“观察者模式”应用中比较牛X的一个框架。当然,框架在编码实现时还用到了其他设计模式,在解析到相关内容时,我们在对其进行概述。

当然,本篇博客是对ReactiveSwift源码的解析,也就是说你可以在你的工程中仅仅的引入 ReactiveSwift框架 ,GitHub地址为:https://github.com/ReactiveCocoa/ReactiveSwift.git,至于如何将ReactiveSwift引入到的你的工程中,请参考ReactiveSwift下方的README, 当然,本篇博客是使用的Cocoapods来实现的版本管理,当然ReactiveSwift也支持Carthage, 如果你是Mac开发的话,还可以使用Swift自带的包管理器。Swift的包管理器我们在之前聊Swift开发服务端的时候使用到了,不过目前iOS开发中还不能使用Swift自带的包管理器。相信在不久的将来Swift的包管理器将会支持iOS开发的。闲淡适中,开始我们的主题。

本篇博客我们将先在Swift语言的层面来聊一些东西,因为在Event和Observer实现时会用到。然后我们再解析一下Event和Observe的实现。之前我们聊过Swift语法层面的东西,不过今天还是要在聊一下的,结合着实例还聊语法最为实用。

一、Swift中的泛型

在ReactiveSwift以及ReactiveCocoa中大量的用到了泛型以及关联类型,所以在聊源码之前,我们还是有必要回顾一下Swift中的泛型的使用的。当然,只是简单的回顾一下,不是今天博客的重点。首先我们得通过一个实例来看一下泛型的使用。

下方这个代码段,就是在协议中使用 associatedtype 关键字声明了一个关联类型,当然这个关联类型就相当于协议中的泛型了。下方的这个 GenericityClass 类后边的<>中声明的就是该类中使用的泛型类型,我们将该泛型命名为 MyCustomType, 当然我们要求该类型必须是遵循 Comparable 协议的类型,所以声明该泛型的形式为 <MyCustome: Comparable>。声明完该泛型后,在类中我们就可以想使用普通类型那样来使用该泛型了。

泛型不仅仅可以在类中使用,也可以在方法中使用,下方的genericityFunc()方法中就使用了泛型,用法就是在方法名的后方紧跟着泛型,如下所示。

  

接下来我们来看一下上述泛型类的使用方式。下方代码首先声明了一个泛型类的实例,在实例化时,给泛型指定了确定的类型 String。我们还可以为相应的的泛型类型使用 typealias 指定别名,然后使用别名来实例化,如下所示。因为代码比较简单,下方测试用例的输出结果就不往上粘贴了。

  

二、Swift中的枚举

因为今天我们要聊的Event就是个枚举,所以我们先来回顾一下Swift中枚举的使用。当然还是依托于实例。下方代码中的枚举是在我们之前聊Swift的枚举的主题中拿过来的,并且做了相应的修改。当然在Swift中枚举以及结构体都是可以使用泛型的,接下来我们就来好好看一下Swift中强大而灵活的枚举类型。

下方代码片段中我们定义了一个MobileLanguage枚举类型,其中有两个枚举项。一个是iOS,另一个是Android。枚举项iOS的枚举关联值是一个含有两个字符串元素的元组,而Android枚举项的关联值是一个字符串。下方的iOSValueandroidValue是两个计算属性,用来返回相关枚举项的关联值。

当然,我们使用 if-case-let语句来获取相关的枚举关联值,具体如下所示。

  

当然,我们还可以对 “==”运算符进行重载,让其支持上述定义的枚举类型的比较。下方主要还是Switch的使用,当然,之前我们也针对过Switch单独进行过讲解,下方就是Switch对元组的匹配,并且在相应的case中获取枚举的关联值,如下所示。

  

下方就是上述枚举的使用与输出结果,如下所示:

  

三、ReactiveSwift中的Event的实现

接下来我们就来分析一下ReactiveSwift框架中的Event枚举的代码实现。我先看其源码,然后再看其使用方式。

1、Event中的事件类型

下方截图中就是Event枚举类型中所包含的所有枚举项。从下方代码中我们可以看出,Event后方跟了两个泛型,一个是Value,另一个是遵循Swift.Error协议的Error泛型。然后紧跟着的是Event枚举中的几个事件类型。下方是对这几种类型的介绍:

  • value: 用来关联信号量所传送过来的值,该值的类型就是上面定义的Value泛型。

  • failed: 表示因错误而被迫中止的事件,其关联值是相关的错误信息。

  • completed: 该事件是完成事件,也就是所有的东西都success,正常终止。

  • interrupted: 该事件表示被迫中断的事件,也就是没有达到预期效果,被迫中止。

  

2、Event中的 isCompleted 和 isTerminating计算属性

这两个属性是计算属性,下方是其实现代码。isCompleted 用来判断该事件是否是正常完成的事件,而isTerminating主要用来判断事件是否已经终止,当然其中包括异常终止。当然这两个计算属性也是比较简单的,就是根据不同的条件返回不同Bool值即可。

  

 

3、Event中的 value 和 error 计算属性

下方这两个也是计算属性,主要是通过 if-case-let 语句来获取枚举的关联值,并与相应的计算属性进行关联。value属性则用来获取枚举项.value所关联的值。而error则用来获取枚举项.failed所关联的值。具体代码如下所示。

  

 

4、Even计算属性的测试

接下来,我们就对上述的计算属性进行测试。下方这段代码就是对上述计算属性的测试。首先我们创建了一个类型为 Event<Int, NSError>类型的事件。该事件所关联的值为100,然后我们输出计算属性value、isTerminating、isCompleted计算属性的值进行打印,具体打印结果如下所示。

然后我们又创建了一个错误类型的事件errorEvent。并给该枚举项关联一个NSError类型的错误对象。然后对error、isTerminating、isCompleted的值进行打印。从打印结果可以看出isTerminating为true,说明是终止事件,而isCompleted为false,则说明是非正常终止。

  

 

5、Event中的map函数

在Event枚举中,主要有两个map函数,一个是map<U>()泛型函数。另一个是mapError<F>()泛型函数。因为mapError<F>()函数的实现与map<F>()函数的实现极为相似,我们此处就以mapError<U>()泛型函数为例。也就是下方这个完整的函数。

map<U>()函数是一个泛型函数,在函数名map后紧跟的<U>就是我们定义的泛型。而该函数的参数是一个闭包 f, 该闭包的类型为 (Value) -> U。也就是说该闭包的有一个Value类型的参数,并且返回一个U类型的返回值。map<U>()这个函数的返回值是一个新的事件,该事件的类型为Event<U, Error>。经过这么一分析,map<U>()函数就是将当前的 Event<Value, Error> 类型的事件映射成Event<U, Error>类型的事件。当然此处的Value和U都是泛型,当然如果换成具体的参数的话,也就是说一个 Event<Int, Error> 类型的参数可以通过下方的方法来映射成 Error<String, Error> 类型的事件。

下方我们需要主要的是返回值 .value( f(value) ) 这句话,.value()的关联值是f(value)这个闭包所返回的值,而f(value)这个闭包的参数是之前事件所绑定的值。而f(value)所返回的值就是要映射的结果类型。f()的闭包体由用户来提供,也就是说用户可以自定义映射规则。

  

 

6、map函数的测试用例

接下来我们来看一下Map函数的使用方式。下方代码段就是Map函数的测试用例以及运行结果。首先我们创建了一个类型为 Event<Int, NSError> 类型的事件,然后该事件的value值为100。 然后我们调用map函数将 Event<Int, NSError> 类型映射成 Event<String, NSError>类型。然后map函数后边跟随的尾随闭包就是我们的映射规则。你可以在该闭包中添加任意的映射规则,将原来的值转换成你想要的值。

  

mapError<F>()函数的实现以及使用方式,与上述函数类似。接下来我们就来看一下mapError<F>()函数的使用方式。首先我们定义了两个错误类,一个是MyError另一个是MyError1。并且定义了一个Event<Int, MyError> 类型的错误事件,然后调用 mapError<F>()函数将其转换成 Event<Int, MyError1> 类型的事件,当然调用时提供的闭包仍然是映射规则。具体如下所示。

  

Event枚举中还有对 == 号运算符的重载,使Event类型的参数支持 == 运算符。其中还有一个将事件类型转换成description描述字符串的 extension。因为其内容比较简单,在此就不做过多赘述了。

四、ReactiveSwift中的Observer

聊完Event的实现,我们来看一下Observer类的实现。Observer的主要职能是对Event进行使用,也就是Observer可以调用自己的方法来发送Event中所提供的各种事件的。下方就是对Observer类的详细解析。

1、Observer类中属性以及构造器的解析

接下来我们来看一下Observer类中所声明的属性以及构造器。首先我们注意到,Observer类也是也一个泛型类,在Observer类名后方分别跟着 ValueError: Swift.Error两个泛型。这两个泛型分别与Event后边的泛型相对应,Value就是事件所关联值的类型,而Error就是发生错误时错误的类型。

紧接着是声明了一个 (Event<Value, Error>) -> Void 的闭包类型,并且为该类型声明了一个Action的别名。然后使用这个Action的别名声明了一个action的不可变属性。而Observer的构造器的参数就是一个类型为(Event<Value, Error>) -> Void 的闭包。

Observer还声明了一个便利构造器。该便利构造器有四个可选类型的参数,每个参数的类型都是一个闭包。这四个可选类型的闭包参数分别与Event中的四种事件相对应,在便利构造器中调用Observer的构造器时,提供了Action闭包的闭包体,在Action闭包体中,根据具体的事件类型来执行便利构造器参数所提供的相应闭包参数。当然便利构造器的闭包参数由Observer的使用者所提供,用来回调相应事件中的值。

  

根据上面的源代码我们不难看出,在初始化Observer的对象时,我们可以调用构造器,也可以调用便利构造器来进行初始化。当然,还是推荐使用便利构造器来实例化Observer类的实例。下方第一个就是使用的便利构造器来实例化Observer的,并且在调用是提供了四个闭包回调,来分别处理Observer发来的不同事件。

当然你也可以直接调用 Observer所提供的构造器,也就是直接为Action闭包赋值。第二段代码中的尾随闭包就是Action的闭包体,当然我们需要自己处理Action针对不同事件是所给出的处理块。

  

2、Observer中发送事件的方法sendXXX()

接下来我们就来看一下Observer中发送各种事件的方法,当然Event有四种事件类型,那么Observer中也就是是4个发送事件的方法了。下方代码片段就是Observer中发送事件的方法,从下方的方法中我们不难看出,发送事件其实就是对 action闭包的调用,并且传入相应的事件。在调用 action 闭包时,就会执行我们所提供的或者遍历构造器中所提供的闭包体,将发送的事件回调出去。

  

3、sendXXX()方法的测试用例

上面我们已经通过Observer的构造器和便利构造器实例化两个实例,接下来我们就调用这些实例所对应的send方法。下方代码片段就是对相应Observer实例的相关send方法的执行。

  

下方就是上述测试用例的执行结果

  

五、Observer工作的流程图

看完上述代码,因为闭包回调会导致一些代码的执行流程已经调用关系不太容易理解,解析来我们就来画一个图来简述Observer的具体工作过程。下方代码就是上述测试用以的执行以及调用的过程。

下方是一个完整的程序执行过程,输入->处理->输出

因篇幅有限,今天的博客就先到这儿,下篇博客我们会继续解析ReactiveSwift框架中的其他内容。

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

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #de38a5 }
span.s1 { }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #ffffff }
span.s1 { }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #ffffff }
span.s1 { }

ReactiveSwift源码解析(一) Event与Observer代码实现的更多相关文章

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

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

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

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

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

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

  4. vueJs 源码解析 (三) 具体代码

    vueJs 源码解析 (三) 具体代码 在之前的文章中提到了 vuejs 源码中的 架构部分,以及 谈论到了 vue 源码三要素 vm.compiler.watcher 这三要素,那么今天我们就从这三 ...

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

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

  6. ReactiveCocoa源码解析(二) Bag容器的代码实现

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

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

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

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

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

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

    上篇博客我们聊完SignalProducer结构体的基本实现后,我们接下来就聊一下SignalProducerProtocol延展中的start和lift系列方法.SignalProducer结构体的 ...

随机推荐

  1. gsoap创建webservice服务简单教程

    版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] WebServicesoapgsoap 使用gsoap创建webservice服务 下载gsop 准备待导出的服务接口定义文件比 ...

  2. UVA 10905 Children's Game (贪心)

    Children's Game Problem Description There are lots of number games for children. These games are pre ...

  3. python特征提取——pyAudioAnalysis工具包

    作者:桂. 时间:2017-05-04  18:31:09 链接:http://www.cnblogs.com/xingshansi/p/6806637.html 前言 语音识别等应用离不开音频特征的 ...

  4. [转]ObjectARX二次开发vs编译器版本ARX版本对应说明

  5. 【vue系列之一】使用vue脚手架工具搭建vue-webpack项目

    对于Vue.js来说,如果你想要快速开始,那么只需要在你的html中引入一个<script>标签,加上CDN的地址即可.但是,这并不算是一个完整的vue实际应用.在实际应用中,我们必须要一 ...

  6. TCP协议简介

    1.TCP协议的特点 TCP是面向连接的运输层协议 每一条TCP连接只能有两个端点.TCP只能进行点对点通信 TCP提供可靠交付的服务.通过TCP连接传输的数据,无差错.不丢失.不重复.并且按序到达 ...

  7. 网页web前端学习技巧

    1. 写js效果时一定要注意先分析好效果的行为,尽量用最简单通用性的代码.分析步骤可以是1.先把要实现的功能一步一步的写在纸上(即自然语言)2.再根据自然语言翻译成机器语言,用jquery写的代码一定 ...

  8. 我总结的常用sql语句

    建表: Set sql_mode='strict_trans_tables':  存储引擎启用严格模式,非法数据值被拒绝 Create table t3(id int(4) primary key a ...

  9. [大数据]-Logstash-5.3.1的安装导入数据到Elasticsearch5.3.1并配置同义词过滤

    阅读此文请先阅读上文:[大数据]-Elasticsearch5.3.1 IK分词,同义词/联想搜索设置,前面介绍了ES,Kibana5.3.1的安装配置,以及IK分词的安装和同义词设置,这里主要记录L ...

  10. Spring+SpringMVC+MyBatis深入学习及搭建(四)——MyBatis输入映射与输出映射

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6878529.html 前面有讲到Spring+SpringMVC+MyBatis深入学习及搭建(三)——My ...