对于函数响应式编程(Functional Reactive Programming),本人第一次接触到是在两年前,当时看到ReactiveCocoa框架对它一时不来电。直到最近抽时间学习了一下Swift 语言,对Swift版本的FRP 框架RxSwift 进行了深入的学习。
1、背景
我们可爱的程序猿在开发的过程中,因为业务需求,在数据流处理或者是用户操作的各个阶段,总是避免不了创建许多临时状态,用来判别、疏导程序的执行顺序。其中,在开发异步程序的时候,这种临时状态所带来的问题尤为明显。
举个例子:我们经常碰到像“键盘精灵”、“搜索引擎”这类异步的搜索功能。当用户输入“0”的时候,程序就应该实时的去请求相应的结果集。这个时候,因为网络的原因数据迟迟未能返回,不过用户还在继续输入想要搜索的更加精准的字符“01”、“012”等等,每一次的输入变化逻辑上都要撤销前一次的请求,再发送请求最新的结果集。到这里还不算复杂,那么,如果当我们的请求失败的时候,就需要编写retry 重试逻辑,请求成功和失败之间的需要维护一堆的临时状态和处理逻辑。
OMG!!! These will be driving me mad. 有木有?有木有更加简单清晰一点方案来救救我吧!!!
那么下面就让我们来见证一下奇迹(Swift 代码):
其中,searchTextField 是用户输入搜索字符的TextField 控件,而searchTextField.rx_text 就好像给这个控件凿开了一个口子,当用户输入字符的时候,字符就像从这个“口子”里流出来的水一样,一滴、两滴、三滴……
这个时候,如果不对水流做一些节制,那么就白白浪费掉了。Throttle 操作函数就像在这个“口子”上装了水龙头一样——开源节流,不随意浪费“资源”。这里的意思是:每次变化都停留了300毫秒后,打开水龙头放水。
当下一次拧开水龙头放水时,如果流出来的字符和上一次一样,这个时候重复发送请求的话,其实是没有意义的。distinctUntilChanged 就是过滤去重的作用,不发送同前一次一样的请求。
flatMapLatest 的Block 中拿到了放出来的“有效”字符串,程序执行到这里才真正去发送API.getSearchResults(query) 网络请求去搜索结果集。刚才说到的失败retry 重试逻辑,在这里就只是一个简单的Operator 操作,Rx 已经帮我们处理好了如何retry。我们只需要关注搜索的结果集,对结果数据进行展示处理救OK 了。
我们知道,临时状态一多就会使我们的代码杂乱甚至难以维护。同样的业务逻辑,这么简短的几行代码,同时又帮助我们解决了常规开发思维中碰到的许多麻烦,而且它没有任何附加的临时状态。
“这是什么鬼,本来要写五百行代码,现在十行都不到,简直就是无视我的KPI啊!!!”。
这就是传说中的函数响应式编程(Functional Reactive Programming),市面上常用的语言都拥有它的身影(RxJS,RxJava,RxCpp,RxPhp,Rx.Net等等),作为一个编程思想的集合体,官方称为ReactiveX(官网:http://reactivex.io),简称Rx。
Rx 是观察者模式(Observer Pattern)的典型应用,所有的异步信息流都可以抽象为一个可观察对象(Observable),需要使用信息流的对象抽象为观察者(Observer)。Observer 根据需要向Observable 发起订阅,即:Observable.subscribe(Observer)。当Observable 有信息到来时,来自全世界的Observer 都能够接收得到。
Rx 的目标是让异步和事件数据流操作更简单。
引用:KVO observing, async operations and streams are all unified under abstraction of sequence. This is the reason why Rx is so simple, elegant and powerful.
2、Rx的优点
1) 线程安全
数据是通过值拷贝的方式在线程中进行传递——值传递,其他线程也有自己的数据Copy,互不牵扯。好比数学中函数,指定传入的参数,也就确定了答案一样“Data in and Data out”,这也就是为什么叫做函数式的由来。
2) 代码更加简洁精炼,开发效率显著提高
正如前面的例子,简单的调用几个操作函数,简化了常规编程中许多临时状态造成的复杂逻辑,减少了大量bug的产生,是开发更加高效。第1、2点和Rx的目标吻合。
3)代替异步操作,用Rx操作函数轻松处理(转换、过滤、合并)
根据Rx异步操作的抽象概念(Observer Pattern),以下的异步操作都可以抽象成可观察对象(Observable)和观察者(Observer)。事实上,Rx已经提供了这些类型的抽象,如Swift语言中,可以参考RxCocoa框架。
代理:Delegate
回调:Block Callbacks
事件:Target Action
通知:Notifications
KVO
4) MVVM 模式最佳粘合剂,让单元测试更得心应手
我们知道MVVM 模式让视图控制器VC 的身板更加精致,VC 和VM 之间的责任边界也非常明确:VM 负责数据处理&VC 负责数据展示。
现在,Rx 和 MVVM结合在一起,除了Bug 随着临时状态大量减少外,单元测试也变的非常容易。Rx 测试框架RxTest提供了一组强大的模拟接口,可以模拟信息流,也就是可以模拟用户操作,非常方便的开发单元测试程序。
3、Rx延伸方向思考
1) 各平台接口用Rx 封装(很多平台&云服务已经对外提供了Rx 接口)
2) 使用MVVM 开发模式,场景化包装可重用VM 模块
→其中1是接口级(我们已经实现),2是VM模块级(包装了业务模型,从UI分离出来)
3) 跨平台开发
用JS 开发各平台UI,使用Native 封装Rx接口(互调性能问题?耦合性高)
界面布局和信息流(RxJS)逻辑用JS实现。Native 开发渲染Render 引擎(有点类似React Native)。
4、总结
看到如此简单的几行代码就能实现原本复杂逻辑而正为之兴奋不已的时候,有没有感觉到像是获得了另一种更加强大的高级技能!!!
那么,我们静下心来捋一捋:Rx之于我们程序员的重要性在于,当PM为用户场景化交互进行激烈地讨(si)论(bi)时,我们程序员(PG)又有了一种可选技能,而且“已然”对各种场景有了最有效的技能应对——拥抱变化,成竹在胸。
5、附录
附上RxSwift中常用的一种可观察对象(Just)源码CSequence图,帮助理解源代码。
蓝色背景的是继承关系的Protocol 或父类,黄色背景的是实体对象类(左边Observable,右边Observer),当可观察对象被订阅Observable.subscribe(Observer) 时,两者之间的交互逻辑。
参考:
Rx官网:http://reactivex.io
Rx可视化工具,帮助理解各种操作函数 :http://rxmarbles.com/
Rx开源Github:https://github.com/Reactive-Extensions
本文作者:罗俊涛(点融黑帮),现任点融网高级移动iOS开发工程师。从最初的嵌入式研发到近几年金融产品研发,对金融产品有比较全面的认识。一直认为“能够专注于技术,翱翔于代码的无边天际”是一件无比幸福的事情。