ReactiveSwift源码解析(五) SignalProtocol的observe()、Map、Filter延展实现
上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于《Signal中的静态属性静态方法以及面向协议扩展》。并且聊了Signal的所有的g功能扩展都是放在Signal所实现的SignalProtocol协议的扩展中的。本篇博客就沿袭上篇博客的内容,我们来聊一下SignalProtocol的部分扩展。本篇博客我们主要来聊一下对Signal添加Observer的observe()方法扩展的具体实现,并且聊一下Signal的Map和Filter相关的功能扩展的具体实现。
当然我们在聊相关源码的具体实现时,会给出相关的测试用例,然后再根据测试用例来理解其代码实现。
一、observe()方法的扩展
首先我们来看一下observe()方法的扩展。通过前几篇博客的介绍,我们知道Siganl与Observer之间的关联是通过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, 并将subscriber与mappedSignal进行关联
最后我们调用signal的observer对象发送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添加到newSignal的observer的action中。
然后使用newSignal的observer的action创建一个类型为Observer<Value, Error>的newObserver对象。
最后将这个新的newObserver对象添加到旧的signal的Bag中。

上述是代码的调用步骤,我们可以看一下具体的执行过程,如下图所示。从下图的结构我们不难看出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
ReactiveSwift源码解析(五) SignalProtocol的observe()、Map、Filter延展实现的更多相关文章
- ReactiveCocoa源码解析(五) SignalProtocol的observe()、Map、Filter延展实现
上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...
- ReactiveSwift源码解析(六) SignalProtocol的take(first)与collect()延展实现
上篇博客我们聊了observe().map().filter()延展函数的具体实现方式以及使用方式.我们在之前的博客中已经聊过,Signal的主要功能是位于SignalProtocol的协议延展中的, ...
- Celery 源码解析五: 远程控制管理
今天要聊的话题可能被大家关注得不过,但是对于 Celery 来说确实很有用的功能,曾经我在工作中遇到这类情况,就是我们将所有的任务都放在同一个队列里面,然后有一天突然某个同学的代码写得不对,导致大量的 ...
- ReactiveSwift源码解析(三) Signal代码的基本实现
上篇博客我们详细的聊了ReactiveSwift源码中的Bag容器,详情请参见<ReactiveSwift源码解析之Bag容器>.本篇博客我们就来聊一下信号量,也就是Signal的的几种状 ...
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- ReactiveSwift源码解析(一) Event与Observer代码实现
ReactiveCocoa这个框架是做什么用的本篇博客就不做过多赘述了,什么是"响应式编程"也不多聊了,自行Google吧.本篇博客的主题是解析ReactiveCocoa框架中的核 ...
- ReactiveSwift源码解析(二) Bag容器的代码实现
今天博客我接着上篇博客的内容来,上篇博客我们详细的看了ReactiveSwift中的Observer已经Event的代码实现.接下来我们来看一下ReactiveSwift中的结构体Bag的实现.Bag ...
- Spring 源码解析之DispatcherServlet源码解析(五)
spring的整个请求流程都是围绕着DispatcherServlet进行的 类结构图 根据类的结构来说DispatcherServlet本身也是继承了HttpServlet的,所有的请求都是根据这一 ...
- iOS即时通讯之CocoaAsyncSocket源码解析五
接上篇:iOS即时通讯之CocoaAsyncSocket源码解析四 原文 前言: 本文为CocoaAsyncSocket Read篇终,将重点涉及该框架是如何利用缓冲区对数据进行读取. ...
随机推荐
- 深入Spring Boot:那些注入不了的Spring占位符(${}表达式)
Spring里的占位符 spring里的占位符通常表现的形式是: <bean id="dataSource" destroy-method="close" ...
- Eureka源码分析:Eureka不会进行二次Replication的原因
Eureka不会进行二次同步注册信息 Eureka会将本实例中的注册信息同步到它的peer节点上,这是我们都知道的特性.然而,当peer节点收到同步数据后,并不会将这些信息再同步到它自己的peer节点 ...
- 微信H5中静默登录及非静默登录的正确使用姿势
在微信中打开网页且需要调用微信登录接口时,微信官方给我们提供了两种登录调用方式:静默登录和非静默登录:但是官方文档中却没有说明在何种情况下使用静默登录,何种情况下使用非静默登录,所以在这里,我想将之前 ...
- datetime日期和时间
datetime是Python处理日期和时间的标准库. from datetime import datetime # 获取当前时间 now = datetime.now() print(now) # ...
- SublimeText 自带格式化代码功能
其实sublime自身就有格式化命令,就不再安装插件,位置在[Preferences]->[Key Bindings]->[User]中, 中文版的位置在 [首选项]->[按键 ...
- 【Windows】关于shift跟空格同时按无反应的解决方案
在玩游戏中我们经常会遇到各种功能键的使用方法 例如shift跟空格,由于我以前经常要加速大跳,突然发现shift跟空格一起按没有效果,语言设置里也没有相关的快捷键设置到,所以网上查询了一番,至此,问题 ...
- 如何用VBS编写一个简单的恶搞脚本
windows系统的电脑, 首先右击桌面,选择新建-文本文档,在桌面上新建一个文本文档: 随后打开计算机或者是我的电脑,点击其中的组织(xp系统多为工具),选择下面的文件夹和搜索选项 ...
- Log4j2中的同步日志与异步日志
1.背景 Log4j 2中记录日志的方式有同步日志和异步日志两种方式,其中异步日志又可分为使用AsyncAppender和使用AsyncLogger两种方式. 2.Log4j2中的同步日志 所谓同步日 ...
- dqname.go
package nsqd func getBackendName(topicName, channelName string) string { // backend names, for u ...
- centos7系统服务管理
systemd是RH7系列操作系统开始启用新的系统和服务管理器.它被设计为与sysv init脚本向后兼容,并提供了一些功能,例如在引导时并行启动系统服务,按需激活守护程序或基于依赖关系的服务控制逻辑 ...