是时候学习 RxSwift 了
相信在过去的一段时间里,对 RxSwift 多少有过接触或耳闻,或者已经积累了不少实战经验。此文主要针对那些在门口徘徊,想进又拍踩坑的同学。
为什么要学习 RxSwift
当决定做一件事情时,至少要知道为什么。RxSwift 官网举了几个例子,比如可以统一处理 Delegate
, KVO
, Notification
,可以绑定 UI,方便网络请求的处理等等。但这些更多的是描述可以用 RxSwift 来做什么,跟为什么要使用 RxSwift 还是会有点不同。
我们先来分析下 GUI 编程的本质,我喜欢把它抽象为视图和数据的结合。其中视图负责两件事:展示和交互,展示什么由数据决定。
其中单向数据流可以通过之前介绍的 ReSwift 完成。看起来好像没 RxSwift 什么事情,其实不然,RxSwift 可以在 UniDirectional Data Flow 的各个阶段都发挥作用,从而让 Data 的处理和流动更加简洁和清晰。
- 通过对 RxCocoa 的各种回调进行统一处理,方便了「Interact」的处理。
- 通过对
Observable
的 transform 和 composite,方便了Action
的生成(比如使用throttle
来压缩Action
)。 - 通过对网络请求以及其他异步数据的获取进行
Observable
封装,方便了异步数据的处理。 - 通过 RxCocoa 的 binding,方便了数据的渲染。
所以 ReSwift 规范了数据流,RxSwift 为数据的处理提供了方便,这两个类库的结合,可以产生清晰的架构和易维护的代码。
当然,前提是对它们有足够的了解,尤其是 RxSwift,也就是我们今天的主角。
什么是 RxSwift
在 GUI 编程中,我认为比较复杂的有三个部分:
- 非原生 UI 效果的实现(比如产品经理们经常冒出来的各种想法)。
- 大量状态的维护。
- 异步数据的处理。
1)不在这次的讨论范畴(这里的学问也很多,比如流畅性和性能)。2) 可以通过单向数据流来解决(结合 Immutable Data)。3) 可以通过 RxSwift 来解决。那么 RxSwift 是如何处理异步数据的呢?
在说 RxSwift 之前,先来说下 Rx, ReactiveX 是一种编程模型,最初由微软开发,结合了观察者模式、迭代器模式和函数式编程的精华,来更方便地处理异步数据流。其中最重要的一个概念是 Observable
。
举个简单的例子,当别人在跟你说话时,你就是那个观察者,别人就是那个 Observable
,它有几个特点:
- 可能会不断地跟你说话。(
onNext:
) - 可能会说错话。(
onError:
) - 结束会说话。(
onCompleted
)
你在听到对方说的话后,也可以有几种反应:
- 根据说的话,做相应的事,比如对方让你借本书给他。(
subscribe
) - 把对方说的话,加工下再传达给其他人,比如对方说小张好像不太舒服,你传达给其他人时就变成了小张失恋了。(
map:
) - 参考其他人说的话再做处理,比如 A 说某家店很好吃,B 说某家店一般般,你需要结合两个人的意见再做定夺。(
zip:
)
所以,从生活中也能看到 Rx 的影子。「有些事情急不得,你得等它自己熟」,异步,其实就是跟时间打交道,不同的时间,拿到的数据也会不一样。可以在线感受下
这里的核心是当数据有变化时,能够立刻知晓,并且通过组合和转换后,可以即时作出响应。有点像塔防,先在路上的各个节点埋好武器,然后等着小怪兽们过来。
RxSwift Workflow
大致分为这么几个阶段:先把 Native Object 变成 Observable,再通过 Observable 内置的各种强大的转换和组合能力变成新的 Observable,最后消费新的 Observable 的数据。
Native Object -> Observable
.rx extension
假设需要处理点击事件,正常的做法是给 Tap Gesture 添加一个 Target-Action,然后在那里实现具体的逻辑,这样的问题在于需要重新取名字,而且丢失了上下文。RxSwift (确切说是 RxCocoa) 给系统的诸多原生控件(包括像 URLSession
)提供了 rx 扩展,所以点击的处理就变成了这样:
1 |
let tapBackground = UITapGestureRecognizer() tapBackground.rx.event |
是不是简洁了很多。
Observable.create
通过这个方法,可以将 Native 的 object 包装成 Observable
,比如对网络请求的封装:
1 |
public func response(_ request: URLRequest) -> Observable<(Data, HTTPURLResponse)> { |
出于代码的简洁,略去了对 error 的处理,使用姿势类似
1 |
let disposeBag = DisposeBag() response(aRequest) |
这里有两个注意点:
Observerable
返回的是一个Disposable
,表示「可扔掉」的,扔哪里呢,就扔到刚刚创建的袋子里,这样当袋子被回收(dealloc
)时,会顺便执行一下Disposable.dispose()
,之前创建Disposable
时申请的资源就会被一并释放掉。- 如果有多个 subscriber 来 subscribe
response(aRequest)
那么会创建多个请求,从代码也可以看得出来,来一个 observer 就创建一个 task,然后执行。这很有可能不是我们想要的,如何让多个 subscriber 共享一个结果,这个后面会提到。
Variable()
Variable(value)
可以把 value 变成一个 Observable
,不过前提是使用新的赋值方式 aVariable.value = newValue
,来看个 Demo
1 |
let magicNumber = 42 let magicNumberVariable = Variable(magicNumber) |
起初看到时,觉得还蛮神奇的,跟进去看了下,发现是通过 subject
来做的,大意是把 value
存到一个内部变量 _value
里,当调用 value
方法时,先更新 _value
值,然后调用内部的 _subject.on(.next(newValue))
方法告知 subscriber。
Subject
Subject
简单来说是一个可以主动发射数据的 Observable
,多了 onNext(value)
, onError(error)
, ‘onCompleted’ 方法,可谓全能型选手。
1 |
let disposeBag = DisposeBag() |