RxSwift 和 RxCocoa 入门
本文主要来自Getting Started With RxSwift and RxCocoa这篇文章
命令式编程 vs 响应式编程
目前大部分面向对象语言都是命令式的编程范式,即通过代码下达命令给系统;虽然可以通过各种技术手段知道数据的变化,但是不能做到自动化地通知。
响应式编程则是想让数据的变化可以自动地通知,你不再需要关心特定状态,这个状态通知到响应的过程,响应式编程(库)已经帮你处理了。
维基百科上有一个更明确的例子:
对于命令式编程,当 a = b + c, a 由 b+c 计算得到;但是之后,b 和 c 发生了变化,这个变化对 a 并不生效,a 还是保持原有的值;而当使用响应式编程时,当 b 和 c 发生变化,程序不需要再次执行 a = b + c 公式,a 的值就会自动更新。
响应式编程库现状
目前分为两派
ReactiveX 主导的 rx 系列库,语言支持如 RxSwift, RxKotlin, 平台支持如 RxAndroid, RxCocoa(iOS);
ReactiveCocoa(GitHub 发起)库,语言仅支持 swift,objective-c,平台仅支持 iOS。
相同点:
都是 Reactive Functional Programming(响应式函数编程)的实现
二者 github star 数不分伯仲,均接近 20k 级别
都有很好的社区支持
不同点:
Hot、Code Signal 实现 API 不同:RAC 设计了两个 api 分别对应 hot、code signal,RxSwift 仅有一个
错误处理,RAC 相对容易一些
UI Bindings,RAC 有不少历史包袱,RxSwift 则更容易使用
Rx 系列支持更多语言和平台
关于这两个库的详细对比,见:ReactiveCocoa vs RxSwift
RxSwift 和 RxCocoa
RxSwift: 响应式编程在 Swift 语言领域的实现库
RxCocoa: 针对 Cocoa 平台(iOS & Mac OS)的响应式编程库
Observables and Observers
一个 Observable 发送变化通知
一个 Observer 订阅一个 Observable,当 Observable 有变化时会被通知
多个 Observer 可以监听一个 Observable,当发生变化时,所有 Observer 都会被通知
DisposeBag
DisposeBag 是 RxSwift 提供的处理 ARC 和内存管理的工具。销毁一个父对象时,会使得 DisposeBag 中的 Observer 对象同时销毁。
当持有 DisposeBag 的对象的deinit()
调用时,每个 disposable Observer 都会取消对监听对象的订阅,这时 ARC 就可以正常回收内存了。
如果没有 DisposeBag 的话,可能会出现两种情况,要么是 observer 会保留下来不被销毁,继续监听;要么被释放,造成崩溃。
建立 Observer 对象时,记得同时添加到 DisposeBag 中,来让 DisposeBag 帮助你回收该对象。
开始吧
先下载示例工程
打开编译后即可看到如下界面
这个例子功能很简单:选择巧克力后,会将其加入到购物车,点击右上角购物车图标可以进入购物车,然后进行结账和模拟支付。
非 reactive 的实现
在 ChocolatesOfTheWorldViewController.swift 中可以看到实现UITableViewDelegate
和UITableViewDataSource
的 extension。
在观察一下updateCartButton()
这个方法,它用来更新购物车中的巧克力数量,在下面两种情况时调用:
viewWillAppear(_:):在 view controller 显示之前
tableView(_:didSelectRowAt:):当点击列表,添加巧克力到购物车时
这就是命令式编程方式实现:你必须手动调用方法来更新购物车巧克力数量。
使用 RxSwift 改写购物车商品数量
购物车商品信息保存在ShoppingCart.sharedCart
这个单例中
定义在 ShoppingCart.sharedCart 中:
虽然可以给其定义中添加一个didSet
闭包,但问题是,这种做法只能在整个数组更新时才能被通知,而不是数组中任意元素变化就可以得到通知。
针对这种情况,RxSwift 提供了解决方案,按照如下方式创建chocolates
变量:
使用 RxSwift 的BehaviorRelay
对象,持有一个 Chocolate 数组类型的值。这么做的目的是:通过BehaviorRelay
对象的asObservable()
可以得到一个 observable,这样我们就可以添加监听者来订阅BehaviorRelay
对象的value
(chocolate 数组)的变化。
上述做法的缺点是,对 chocolate 数组的修改必须修改为使用accept(_:)
,这是BehaviorRelay
为修改value
属性提供的方法。由于对数据的访问方式发生了变化,代码中相应的地方也要做修改。
在 ShoppingCart.swift 中
totalCost()
方法的修改:
itemCountString()
方法的修改:
在 CartViewController.swift 中
reset()
方法的修改:
在 ChocolatesOfTheWorldViewController.swift 中
updateCartButton()
方法的修改:
tableView(_:didSelectRowAt:)
方法的修改:
完成上述修改后,我们就可以来对chocolates
添加 observer 了。
在 ChocolatesOfTheWorldViewController.swift 中,新增:
在//MARK: Rx Setup
的 extension 中添加:
上述代码即可实现对购物车的自动更新。
可以看到,RxSwift 大量使用函数链,就是说每一个函数接收前一个函数的结果。
对上述代码的解释:
从购物车中
chocolates
得到一个Observable
对
Observable
调用 subscribe(onNext:)来监听其值变化。subscribe(onNext:)
接收的闭包在每次值变化时都会被执行。闭包中的入参就是Observable
变化后的最新值。除非你取消订阅,或者 dispose 订阅,你会一直收到变化通知。这个方法的返回的一个Disposable
(也是一个Observer
)。将上一步返回的
Observer
加入到我们的定义disposeBag
中。这会让被订阅对象被销毁时,订阅者也被处理。
最后,删除updateCartButton()
方法的调用。
然后执行代码,你会看到巧克力列表:
但此时,点击单个巧克力,购物车位置一直显示的是“Item”。这是因为setupCartObserver()
没有被调用,导致Observer
并没有建立起来。在 ChocolatesOfTheWorldViewController.swift 的viewDidLoad()
方法最后调用该它。
再次编译运行,就会看到,当点击巧克力时,购物车自动更新了。
使用 RxCocoa 对 TableView 进行 Reactive 改造
RxCocoa 为原生 UI 组件添加了响应式 API。这本例中,使用 TableView 的响应式 API,可以不再用自己实现UITableViewDataSource
和UITableViewDelegate
。
实现步骤:
第一步:代码中删掉 data source 和 delegate 相关代码。
第二步是将 table view 使用到的数据从数组,改为一个 Observable:
just(_:)
表明Observable
持有的值(value)是不变的。
注:对于不变化的值,是没有必要使用响应式编程的。所以在实际应用中,要避免拿着锤子看什么都是钉子,要实际分析一下你是否真的需要使用 Rx。
第三步在//MARK: - Rx Setup
添加如下代码:
代码释义:
调用
bind(to:)
将europeanChocolates
这个 Observable 关联到 table view 每行所要执行的代码上调用 rx 后,让你可以访问 table view 的 Rx 扩展
代用 Rx 方法
items(cellIdentifier:cellType:)
传递相关参数。这样 Rx 框架根据这些信息实现一个 wrapper data source,再设置给 table view。当拿到了新的 cell item 时,通过这个 block 来进行设置
将 bind 方法结果加入到 disposeBag
再在 viewDidLoad 中调用下上面的 setupCellConfiguration()方法。
这时运行程序,就会再次看到巧克力列表数据。
第四步来添加事件处理:
在//MARK: - Rx Setup 位置添加如下代码
代码释义:
modelSelected(_:)
传入 Chocolate model 类型,得到 Observable 对象subscribe(onNext:)
是给 Observable 对象注册事件处理,这个闭包代码会在一个 model 被选中时调用闭包中首先将选中的 chocolate 加入到购物车
反选 table row
将
subscribe(onNext:)
返回的Disposable
加入到disposeBag
最后在 viewDidLoad 中调用setupCellTapHandling()
然后运行,就可以看到经过 Rx API 重写过的样例功能,和之前一样:点击列表中的巧克力,将他们添加到购物车中。
使用 RxSwift 处理用户输入(Direct Text Input)
RxSwift 也可以用来处理用户输入和表单验证。
非 Reactive 的做法是,实现UITextFieldDelegate
,然后实现很多if/else
来分别处理输入及其对应的动作。
Reactive Programming 的做法则是更直接地将输入处理动作和逻辑绑定到 input field 上。
我们可以通过下面这个例子来具体体会一下。
先在 BillingInfoViewController.swift 中创建一个 DisposeBag:
然后在//MARK: - Rx Setup
评论下的 extension 中添加如下代码:
代码释义:
给
BehaviorRelay
的值添加一个Observable
订阅这个
Observable
来获得cardType
的变化将 Observer 的销毁交给
disposeBag
上述代码就实现了一个 Reactive 的例子:当 cardType 变化时,creditCardImageView 的图片会变成对应 type 定义的图片。接下来实现对文字变化的处理。
考虑到用户输入可能会很快,如果每次输入都执行验证代码可能会导致 UI 卡顿。所以我们需要实现一个 throttle 来控制,在 throttle 的时间间隔后才会再次验证输入,这样就可以避免对 UI 的阻塞。
RxSwift 是支持 Throttling 的,因为有不少场景需要控制对变化的响应频次。下面我们来看看如何实现。
首先,在 BillingInfoViewController 中定义一个常量来表示 throttle 的间隔毫秒时间:
然后,在RX Setup
extension 中加入下面的代码:
代码释义:
text 是 RxCocoa 的扩展,它从一个 UITextField 获得其 value 的一个 Observable
基于之前定义的 throttle 时间间隔,创建一个 throttle;scheduler 目前先绑定到主线程
将结果交由给
validate(cardText:)
,它可以将输入转换为 creditCardValid 的值,如果输入有效,creditCardValid 的值会是 true监听 creditCardValid,根据其结果来改变 text field 的状态
销毁事宜
实现 CVV 的校验和上述思路一样。
先在setupTextChangeHandling()
底部添加如下代码:
最后,将三个 text field 结合起来做验证,在setupTextChangeHandling()
加入以下代码:
combineLatest(_:)将三个 observable 结合到一起再生成一个 observable,everythingValid 的值会是 true 或 false。接着再讲 everythingValid 关联到 purchaseButton 的 rx 扩展属性 isEnabled,以此来实现对该 button 是否可用状态的控制。
最后,在 viewDidLoad 中添加如下代码:
运行代码,然后选择巧克力后,进入购物车:
然后点击 checkout,进行支付界面:
这里可以看到,输入 4,后面图标就显示了 visa 的图标。
接着,输入合法的 CVV 和有效期限,Buy Chocolate 按钮就是可用状态了:
至此,我们就实现了一个简单的对用户输入以 reactive 方式进行校验的示例。
评论