上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于《Signal中的静态属性静态方法以及面向协议扩展》。并且聊了Signal的所有的g功能扩展都是放在Signal所实现的SignalProtocol协议的扩展中的。本篇博客就沿袭上篇博客的内容,我们来聊一下SignalProtocol的部分扩展。本篇博客我们主要来聊一下对Signal添加Observer的observe()方法扩展的具体实现,并且聊一下Signal的MapFilter相关的功能扩展的具体实现。

当然我们在聊相关源码的具体实现时,会给出相关的测试用例,然后再根据测试用例来理解其代码实现。

一、observe()方法的扩展

首先我们来看一下observe()方法的扩展。通过前几篇博客的介绍,我们知道SiganlObserver之间的关联是通过observe()方法来实现的。而observe()方法的核心实现在上篇博客中已经进行了详细介绍。而在协议扩展中又对observe()方法进行了一些扩展,这些扩展主要是针对一些特定功能为observe()的使用方式添加快捷调用方式。

1、observe()方法扩展的具体实现

下方个SignalProtocol的延展主要是对observe()方法的扩展,在每个扩展方法中最后还是得调用Signal类中所实现的observe()方法。还是那句话,下方的这些observe()方法的扩展主要还是Signal类中observe()方法的快捷方式。下方将对observe()的每个快捷方法进行介绍。

  • observe(action) : 该方法传入的是一个Action闭包,该Action闭包其实就是Observer类中的Action闭包。也就是在调用observe(action)方法时,为Observer的Action提供了闭包体。而在observe(action)方法中要做的就是实例化Observer对象,并将该对象传给Signal的observe()方法。稍后会给出该扩展方法的使用过程。

  • observeResult(result):该扩展方法是将Observer的value事件和failed事件分别转换成Result枚举的success和failure。

  • observeCompleted(completed): 该扩展方法所接收的闭包就是Observer接收completed事件所执行的闭包。

  • observeFailed(failed): 该扩展方法所接收的闭包就是Observer接收failed事件所执行的闭包。

  • observeInterrupted(interrupted): 该扩展方法所接收的闭包就是Observer接收interrupted事件所执行的闭包。

从下方代码片段中我们不难看出,都是在Signal的observe()方法的基础上做的扩展,本质上就是observe()方法的特定使用的快捷方式。

  

2、上述扩展的使用方式

看完实现,在看上述方法的使用方式就简单多了。下方代码片段就是上述扩展中每个方法的使用方式。我们可以根据具体的业务场景以及具体的功能来选择实现那种方法来满足自己的开发需求。从下方每个方法中的调用方式可以看出,每个方法在调用时所提供的尾随闭包就是该方法所表示的快捷方式。

当然,下方所有的方法,我们都可以使用Signal中的observe()方法来实现,只不过没有下方这些方法方便快捷。

  

二、SignalProtocol的Map扩展

在《ReactiveSwift源码解析之Event与Observer》这篇博客中我们聊了Event的Map函数,主要是将一个类型的Event(如Event<Value, Error>)转换成另一个Event类型(如Event<U, Error>)。Signal的Map函数也不例外,也是将一个类型的Signal转换成另一个类型的Signal。当然,Signal的Map函数本质上还是使用了Event的Map函数。

根据之前对ReactiveSwift框架的解析,我们不难发现Signal、Observer以及Event三者要想进行沟通是其泛型类型必须是相同的,也就是一套的。Map函数就是为了解决Signal类型与Observer的类型不匹配而生的。也就是说我们可以通过Map函数的处理,一个Int类型的Signal可以发送给String类型的Observer的,如果没有Map函数的支持,是做不到这一点的。

可以说Map函数是“适配器模式”的一种应用方式。可以将不同类型的信号量和观察者进行适配使其正常通信。接下来我们就来看一下SignalProtocol协议的Map相关的扩展以及使用方式。

1、map<U>() -> Signal<U, Error> 映射函数

map<U>()就是扩展中的Map相关函数之一。该函数是一个泛型函数,其返还值是一个Signal<U, Error>类型的对象。也就是说,一个Signal<Value, Error>类型的信号量可以通过map<U>()函数映射成一个Signal<U, Error>类型的信号量。当然map<U>()函数的参数是一个尾随闭包,该闭包有map函数的调用者提供,目的就是为了让用户自定义两个信号量之间的映射规则。

首先我们来看一下map函数的使用方式,下方代码片段中是map函数的使用示例以及输出结果,下方是对这段代码的解释:

  • 首先我们通过Signal的pipe()静态方法创建了一个类型为Signal<Int, NoError>的信号量signal。

  • 然后通过signal的map函数创建了一个新的类型为Signal<String, NoError>的信号量mappedSignal。map函数的尾随闭包中就是映射规则,其中value是Int类型,而返回值是String类型。

  • 然后创建了一个Observer<String, NoError>类型的观察者subscriber, 并将subscribermappedSignal进行关联

  • 最后我们调用signalobserver对象发送value事件,该事件所携带的值为整数10。由输出结果我们可以知道,与mappedSignal关联的观察者subscriber尽管只接收String类型的事件,但是经过map函数的处理此刻也是可以收到来自signal的整数值信号量的。

  

看完map函数的用法后我们来看一下其具体的代码实现。下方就是上述示例所调用的map()函数的具体实现代码。在map()函数中返回了一个类型为Signal<U, Error>的信号量对象。在Signal的构造器的尾随闭包中又调用了observe(action)方法将新创建的Signal的observer对象所对应的action添加到了之前Signal对象中。

  

上述代码的执行过程也许有些绕,我们可以通过一张简图来看一下上述代码的执行过程。下方是对该过程进一步的解释:

  • 首先类型为Signal<Value, Error>的signal对象调用其map<U>()函数生成了一个新的类型为Signal<U, Error>的newSignal对象。

  • 然后通过Event的map<U>()函数,将signal对象中类型为Event<Value, Error>的事件通过调用事件的map<U>()函数将其映射成类型为Event<U, Error>的newEvent事件对象。

  • 然后我们将新的newEvent添加到newSignalobserveraction中。

  • 然后使用newSignalobserver的action创建一个类型为Observer<Value, Error>的newObserver对象。

  • 最后将这个新的newObserver对象添加到旧的signalBag中。

  

上述是代码的调用步骤,我们可以看一下具体的执行过程,如下图所示。从下图的结构我们不难看出map()函数是链式发展的,下发的mappedSignal还可以调用其自己的map()函数来生成新的Signal对象。在这个链上的所有Observer都会接受到最原始的Signal对象所发出的事件消息。signal与mappedSignal桥接的最终手段还是Event的map()函数。

2、mapError<F>() -> Signal<U, F> 映射函数

mapError<F>()映射函数的实现机制和使用方式与上述的映射函数即为相似。只不过该映射函数使用了Event的mapError<F>()函数。因该映射还是的实现方式与上述函数类似,在此就不做过多赘述了。

关于lazyMap<U>()的实现和使用方式,我们暂且不说,后边聊到SignalProducer以及Flatten时我们再做补充。

三、SignalProtocol的filter扩展

Filter顾明思议,就是用来过滤东西的。如果你理解上述map的工作原理的话,Filter就显得简单多了。Filter的工作原理以及实现方式与map相似,只不过将Event的map改成了过滤条件。首先我们将会给出Filter的使用方式,然后在该处Filter的代码实现方式并给出工作原理图。

1、Filter的使用方式

关于Filter的使用,我们就使用ReactiveSwift官方的示例,下方是对该示例的解释:

  • 首先使用pipe创建一个signal,然后获取到该signal发送消息的句柄observer。

  • 然后通过调用signal的filter()函数来获取过滤信号量filteredSignal,filter()函数的尾随闭包中跟着的是过滤条件。

  • 然后往filteredSignal信号量中添加观察者subscriber。

  • 当使用signal信号量发送事件时,符合过滤条件的事件才会被过滤信号量filteredSignal所关联的观察者接收

下方截图中我们的过滤条件是事件绑定的值必须大于12,也就大于12的Value事件才会被观察者接受,所以输出的结果只有13和14两个,具体如下所示。

  

2、Filter的代码实现

看完Filter的使用方式,接下来我们来看一下Filter的代码实现方式。下方代码片段就是filter函数的具体实现,从代码结构上来看,与上述的map函数差不多,都是返回一个新的Signal对象,新的Signal对象与原来的Signal对象之间有一个桥接观察者来进行通信的。self.observer()函数后边的闭包就是桥接观察者从原信号量中发出的事件,然后在该事件中根据过滤条件来判断是否向新的信号量所绑定的所有观察者转发该事件。

从下方代码中我们明确的可以看出,当条件闭包predicate()的值为true时,observer就会对值的事件进行转发,然后过滤信号量所绑定的观察者就可以收到这些事件了。

  

3、执行原理图

下方就是filter的执行原理图,该图的结构与map()函数的执行结构类似。从下方图中我们不难看出,filter()函数也是支持链式发展的,就是可以在新的filterSignal的对象上我们任然可以添加新的过滤条件条件。因为无论是map()还是filter()函数都会返回一个新的Signal对象,并且两者都是可以链式发展的,所以我们可以这样去写signal.map().filter().map().filter().filter()……

在filter下方还有一个filterMap<U>()函数,该函数的主要功能是用来Map的,代码实现方式与与上述的filter类似,只不过是map的功能,但是该map功能有过滤功能,可以过滤掉nil的值。扩展中的skipNil()方法中调用的就是filterMap<U>()函数,在此就不做过多赘述了。

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

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

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: #08fa95 }
span.s1 { }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #ffffff }
span.s1 { }

ReactiveCocoa源码解析(五) SignalProtocol的observe()、Map、Filter延展实现的更多相关文章

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

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

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

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

  3. Celery 源码解析五: 远程控制管理

    今天要聊的话题可能被大家关注得不过,但是对于 Celery 来说确实很有用的功能,曾经我在工作中遇到这类情况,就是我们将所有的任务都放在同一个队列里面,然后有一天突然某个同学的代码写得不对,导致大量的 ...

  4. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

  5. ReactiveCocoa源码解析(三) Signal代码的基本实现

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

  6. Spring 源码解析之DispatcherServlet源码解析(五)

    spring的整个请求流程都是围绕着DispatcherServlet进行的 类结构图 根据类的结构来说DispatcherServlet本身也是继承了HttpServlet的,所有的请求都是根据这一 ...

  7. iOS即时通讯之CocoaAsyncSocket源码解析五

    接上篇:iOS即时通讯之CocoaAsyncSocket源码解析四         原文 前言: 本文为CocoaAsyncSocket Read篇终,将重点涉及该框架是如何利用缓冲区对数据进行读取. ...

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

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

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

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

随机推荐

  1. 【基础】新手任务,五分钟全面掌握JQuery选择器

    1. 基本选择器 1.1 ID选择器: //选中id为myDiv的元素,速度最快 $("#myDiv") 1.2 类选择器: //选中class属性为red的所有元素 $(&quo ...

  2. Windows Server 2016中,安装PHP Manager,ARR3.0或者URL Rewrite 2.0无法成功的解决办法

    如图: 无法安装原因都是这几个工具无法识别10.0这个版本,可以修改注册表来先完成安装,然后再改回去 PHPManager的修改方法如下: 打开注册表工具(运行Regedt32),找到:HKEY_LO ...

  3. 导出CSV,导出excel数字过长显示科学计数法解决方案

    再导出CSV表格时发现数字超过一定长度后会缩写成科学计数法,对于手机号和身份证就比较尴尬了. 在网上找了一下,大部分都是加"'"将数字转换为字符串,但是纠结于导出的数字前面有个单引 ...

  4. Python yield用法

    yield 官方称是一种生成器,每每遇到这样包含这个关键字的代码,往往有些难读.def testyield(count): for x in xrange(count): print "te ...

  5. 机器学习:Python实现聚类算法(一)之AP算法

    1.算法简介 AP(Affinity Propagation)通常被翻译为近邻传播算法或者亲和力传播算法,是在2007年的Science杂志上提出的一种新的聚类算法.AP算法的基本思想是将全部数据点都 ...

  6. netty基础--基本收发

    使用maven构建一个基本的netty收发应用,作为其他应用的基础.客户端使用packet sender工具. 1  添加netty依赖 1  maven netty依赖 <dependency ...

  7. C#码农的大数据之路 - 使用Azure Management API创建HDInsight集群

    Azure平台提供了几乎全线产品的API,可以使用第三方工具来进行管理.对于.NET更是提供封装好了的库方便使用C#等语言实现Azure的管理. 我们使用创建HDInsight集群为例来介绍使用C#管 ...

  8. 新手如何快速入门Python

    学习任何一门语言都是从入门(1年左右),通过不间断练习达到熟练水准(3到5年),少数人最终能精通语言,成为执牛耳者,他们是金字塔的最顶层.虽然万事开头难,但好的开始是成功的一半,今天这篇文章就来谈谈如 ...

  9. 排序算法 - 插入排序(Insertion sort)

    插入排序对于少量元素的排序是很高效的,而且这个排序的手法在每个人生活中也是有的哦. 你可能没有意识到,当你打牌的时候,就是用的插入排序. 概念 从桌上的牌堆摸牌,牌堆内是杂乱无序的,但是我们摸上牌的时 ...

  10. js脚本都可以放在哪些地方

    js脚本应该放在页面的什么地方 1.head部分 包含函数的脚本位于文档的 head 部分.这样我们就可以确保在调用函数前,脚本已经载入了. 2.body部分 执行位于 body 部分的脚本. 3.外 ...