关于 RxSwift 的入门学习,在官方 GitHub 仓库上已经提供了很多文档。另外,同样有对应的中文文档释出。
本篇是谈一下我对 Observable 和 Operator 的一些认识。先提一点题外的,关于 side effects。
Side effects
Side effects 指的是由于更改了当前作用域之外的共享状态值,从而产生的“副作用”。例如,你可能在实际项目里写这样的代码:
override func viewDidLoad(_ animated: Bool) {
super.viewDidLoad(animated)
setupUI()
bindUI()
listenForChanges()
}
在 bindUI 中,你可能给特定的 UI 控件绑定了相应的动作。那么,这就产生了一个 side effect:你的 app 在 bindUI 之前以及之后产生了不一样的行为。但是需要明确的是,产生 side effect 并非是不好的!毕竟我们就是需要这些 side effects 从而达到我们想要的效果。我们只是需要对这些 side effects 有一个合理的控制(in a controlled way)。
再延伸一点,在命令式编程(Imperative Programming)中,常会更改外部的共享状态值。而在函数式编程中,是不会更改外部的共享状态值的,从而也就不会产生 side effects。而在 RxSwift 中,结合了二者的优点,通过其典型的注册(Subscribe)的方式,以有顺序、声明式的方式对不可更改的数据做处理,提供了解决异步编程难题的一种优雅方案。在深入探讨 RxSwift 之前,需要对 Observable 和 Operator 有个清晰的认识。
Observable
Observable 应当是 RxSwift 里最核心的概念。它可以发出(emit)三种事件(event):
值得注意的一点是,Observable 只有被 subscribe 之后才会真正地发出事件。另外,Observable 接收到 terminated 事件的时候,当前对应的 Subscription 会被 dispose 掉。
Operator
ReactiveX 提供了众多 Operator 运算符,RxSwift 当然也不例外。这些运算符基本涵盖了日常开发时的运用场景,例如 map, filter, skip 等等,详细的介绍以及实例在官网上也有。在这里着重比较一下 map 和 flatMap 在 Swift 和 RxSwift 中的不同。
Map
在 Swift 中,map 是 Sequence 协议中的方法,其作用是对 Sequence 中的元素按照转换函数分别进行映射,并返回映射后的 Sequence。拿 Array 举例:
[1,2,3].map { (num) -> String in
return "\(num)"
}
上述 map 函数将会返回 ["1","2","3"]。
在 RxSwift 中,map 将 Observable 中发出 next 事件中的 element 按照给定的转换函数进行映射,并返回映射后的 Observable。用图来解释就是:
FlatMap
关于 Swift 中 flatMap 是什么,我推荐看flatMap 温顾知新 —— 参照 Swift 源码实现讲解这篇文章,解释地十分详细;进阶版解释可以看Swift 烧脑体操(四) - map 和 flatMap。
个人觉得 RxSwift 中的 flatMap 稍微复杂了一点,ReactiveX 的官方文档是这么定义它的:
FlatMap: transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable
也就是说,当一个 Observable 拥有一个 Observable 的子属性时,flatMap 会对子 Observable 的 element 进行变换,然后将变换后的子 Observable 重新展平(flatten)成一个 Observable 并返回。举一个例子:
我们有一个 Student 模型,它的结构是这样的:
struct Student {
var score: Variable<Int>
}
在下面的代码中,定义了两个学生 ryan 和 charlotte。而 student 这个 Subject 它是 Student 类型的(Subject 是什么在以后会提到,你现在只需要知道它既是一个 Observer 也是一个 Observable),并对其进行了 flatMap 以及 subscribe,其作用是打印出这个学生的成绩:
let ryan = Student(score: Variable(80))
let charlotte = Student(score: Variable(90))
let student = PublishSubject<Student>()
student.asObservable()
.flatMap {
$0.score.asObservable()
}
.subscribe(onNext: {
print($0)
})
.addDisposableTo(disposeBag)
在之后的时间里,如果发生了下面的事件:
student.onNext(ryan)
ryan.score.value = 85
student.onNext(charlotte)
ryan.score.value = 95
charlotte.score.value = 100
将会打印出 80,85,90,95,100。也就是说,最终的效果是,它同时可以“监听”多个 Observable 中的子 Observable 产生的变化,用图表示是这个样子(为了方便手绘):
那么如果你想要只“监听”最新加入的 Observable 的子 Observable 应该怎么办呢?RxSwift 提供了 flatMapLatest 方法,也就是说,改用 flatMapLatest 后上面的输入将会打印出 80,85,90,100。
One more
特别的,flatMap 还适合解决异步返回的问题:(例子来源于靛青K)
Observable.just(1)
.map { $0 * 2 }
.flatMap { value -> Observable<String> in
return Observable.create { observer in
DispatchQueue.main.async {
observer.onNext(String(value))
observer.onCompleted()
}
}
}
这也是为什么在 RxSwift 的实际使用中,flatMap 出镜率比较高的原因之一。


