讲解RxSwift用法的文章很多,我这里想分享下RxSwift的设计的思想。RxSwift的核心思想是以链式结构编程。应用到前端UI框架中的话,就是把数据以流式结构映射到UI上,每个UI元素都对应一个数据源的操作流。开发者阅读代码就更加的清晰,修改功能也更加方便。如何才能达到上面说的目的那?下面简单的分析一下。
RxSwift设计哲学
当前我们用的编程语言,大多是命令式的编程方式,命令式强调的是解决问题的过程。而流式结构本质上是一种函数映射的关系,强调的是解决问题的方法。下面来举个例子解释下,比如要把一个数组中的数字都除以2,用命令式的方式如下:
var list = [2,4,6,8]
var newList: [Int] = []
for num in list {
let result = num / 2
newList.append(result)
}
从上面的代码可以看出来,解决的过程非常清晰,从数组中每个元素取出来,然后除以2之后,再塞给另一个数组,完全符合计算机解决问题的逻辑。 下面来看下用函数式的方式解决这个问题如下:
var list = [2,4,6,8]
let newList = list.map { (num) -> Int in
return num/2
}
首先这个代码看起来会简洁一些,为何会简洁,是因为函数式并不会用计算机解决问题的方法,把所有过程给表述出来,上面的代码只是表述了一种函数映射关系 list -> newList,变量list通过一个函数映射出结果newList。
其实这种方式更符合人类的思维,因为人类发明数学,函数就是解决问题的一个重要手段,往往函数映射是线性处理一个关系的,更容易在人脑里形成逻辑,而计算机解决问题的方式,往往是跳跃的,不停的控制变换的变量,很容易让思绪混乱。所以我在想如果人一开始就不是用命令式的方式编程,而是用函数式方式,编程应该会更加接近解决问题的方法,而不用思考计算机硬件繁琐的处理数据方式,编程也会更加优雅和易学。
下面就引申出另一个问题,函数式编程的方式,能完全表达出计算机命令式的所有逻辑么?其实已经有科学家证明了这个问题,他们是完全等价的。至于如何证明可能已经超出了我的认知范围😹。不过有兴趣的可以看下Lambda演算。其实本质上Lambda演算和图灵机是对等的,所以图灵机可以解决的问题,理论上用Lambda演算都可以解决。好了理论证明完,下面就介绍下函数式编程的一些核心思想
函数式编程
凡是提到函数式编程,几乎都要提及这三个重要概念 functor,Applicatives,monad。因为实现了这三个概念的编程语言,就符合了上面提到的Lambda演算,就可以用函数式映射的方式进行编程,上面的链接详细的解释了这三个概念,下面我就简单的总结下:
- functor本质上是把封装的数据通过函数λ映射到另一个封装的数据 packageA ->(λ) packageB
- Applicatives是可以把封装过的函数packageλ应用到封装过的数据中,得到另一个封装过的数据 packageA ->(packageλ) packageB
- monad的实现是函数式很核心的功能,如果仅仅实现了上面说的两个方式,还不足以让数据实现流式操作,比如遇到封装过的函数packageλ,如何通过另一个函数映射出结果哪?这里就需要monad的出现了,monad可以把封装过的packageλ,通过函数映射成封装过的数据。至此所有的操作都可以通过这三种方式做映射,也就可以实现流式编程。
其实monad这个概念是范畴学中的一个重要理论,下面就简单的描述下这个概念吧。
monad概念
这里我只是按照我个人的理解,描述下monad这个概念如何应用到编程语言上的,如果想了解深层次的理论,还是建议大家学习下范畴学中自函子的概念。
monad如果用数学语言来描述的话,就是把一个范畴的对象提升到了另一个新的范畴,在编程语言中,老的范畴代表的是什么,就是命令式的逻辑控制(for,if,while)和数据结构。新的范畴是可以把老的范畴映射到新的范畴中,同时新的范畴也可以解封成老的范畴,并且新的范畴之间也可以互相映射。
范畴学中的自函子应用在编程语言中,就是用代码线性地描述新对象和新态射的关系并且可以复合态射,本质上就是可以用定义的一个类型,通过函数映射,还能成为同一个类型。
RxSwift的结构
好了,上面准备好了理论,我们看下RxSwift的核心结构,本质上RxSwift里面的Observal就是monad的类型,也就是上面提到的自函子,所以理论上来讲所有的命令式的程序,都可以用observal线性的方式表达出来,也就是省去了好多命令式处理数据的过程,让解决的方法更清晰一些,对于数据操控UI显示的程序上面,尤其有用。
Observal做为monad类型,当然要具备让老的范畴映射到新的范畴的能力,例如通过create的方法,这里可以称之为升维操作,同样也可以把新的范畴映射到老的范畴中,例如subcribe方法,可以称之为降维操作。同样RxSwift提供了大量的函数操作可以让observal对象之间映射,例如merge,flatmap。下面我就简单的介绍下RxSwift类的构造。
上图可以看出来,RxSwift三个核心的类Observal,Observer,Subject。当然Observal是最核心的,而Observer实现的是观察者模式,而RXSwift也正是通过Observer这个方式,来方便的实现升维和降维操作,如果有兴趣可以看下源码Producer这个类里面包含了一个关键的类sink,这个在C++语言中很常见的泛型的槽,其实本质上就是利用Observer对象,实现升维和降维的操作。
另一个我要说的是Subject类型,RxSwift里面有很多种Subject类型,例如PublicSubject,RelaySubject,BehaviourSubject。他们都是继承自Observal,但是他们也具备Observer的功能,所以如果想方便的进行Observal创建和订阅,可以使用Subject这些类型。
了解了上面这些核心概念后,最后就需要掌握大量的Observal之间互相的操作函数了,例如flatmap,merge,contact等等,这里我就不详细的描述了,网上有大量的文章描述这个,我这里主要目的还是想分享下作者设计的思路。
上面介绍完RxSwift的设计思想后,后面想分享下在学习RXSwift自己经常遇到的一些疑惑,以及最近RXSwift升级到5.0新增的一些有用的特性。
RXSwift使用
用观察者模式就可以替换RXSwift了么?
自己最初在学习RxSwift的时候,基本把RxSwift当成观察者模式的替代品使用了,只有绑定数据的时候使用,其实这真的是杀鸡焉用牛刀,如果仅仅是为了这个用途的话,像KVOController就可以替代RxSwift,观察者模式本质上只是某个数据绑定到了一个UI上,但是数据的操作方式,还是要用命令式的程序来组合,想要达到从源头链式的映射到UI上面很难。RxSwift的核心思想是要所有的功能都可以流式操作,不过观察者模式本质上用RxSwift可以方便的实现,甚至RxSwift还可以替代Promise这种常用的异步控制框架。
为何所有的NSObject要定义rx这个属性?
因为RxCocoa把很多ios的基础库控件做了升维操作,方便开发者使用,如果开发者想让某个控件具有RxSwift的能力,只要扩展Reactive这个类就可以了,在RxCocoa有大量的控件扩展了这个类。例如:
extension Reactive where Base: UIButton {
/// Reactive wrapper for `TouchUpInside` control event.
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}
RxSwift废弃Variable
在RxSwift5.0中作者完全遗弃了Variable这个类,之前这个类可以用来做双向绑定,然后被很多开发者当成观察者模式来使用,程序逻辑用了大量的命令式的方式来开发,这完全违背了作者设计的初衷。作者最开始都说过Variable这是方便开发者从命令式编程向函数式编程过渡的类,之后会废弃掉,现在5.0已经完全不能使用了。
RxDatasouce框架增加到了RXCocoa框架中
在5.0之前,如果UITableView和UICollectionView想要使用RXSwift的流式编程方式的话,需要引入RXDataSouce框架,这个框架本质上把UITableView和UICollectionView数据源的所有的delegate操作,用RxSwift的函数式方式实现了,之后就可以方便的实现数据源的流式组装。
下面可以思考下,如果你需要写一个collectionView的类,然后不让你的类中使用全局变量来存储数据源,你是什么方式可以实现那?当然你要问为何不用全局变量那,全局变量我可以很方便的操作list啊,但是同时也引入了大量的问题,你不知道list那里会被操作,在编写代码的时候经常会遇到不同的地方在删除list中的数据,而有些地方又要增加list的数据,然后造成数据不一致crash。那如果用RxSwift来写一个基于collectionView的controller,就可以达到完全不用全局变量的效果。只要简单的几行下面的代码就可以实现:
let items = Observable.just([
1,
2,
3
])
items.bind(to: collectionView.rx.items) { (collectionView, row, element) in
let indexPath = IndexPath(row: row, section: 0)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell
cell.numberLabel.text = "\(element) @ \(row)"
return cell
}
.disposed(by: disposeBag)
总结
本文中的一些代码例子,就可以体会到使用RxSwift编程的简洁和优雅。RxSwift更强调的是解决问题的思路,是不同于命令式程序强调的解决问题的繁琐过程的。而线性的流式操作,尤其在做数据和UI渲染的开发中尤其有用,开发者很容易就可以知道UI元素变化对应的数据源。再加上Swift语言本身对高阶函数支持的比较完善,所以RxSwift这种编程思想相信之后会越来越流行。