摘要
本文主要是为了解决 Rx 与 Cell 之间的各种坑的问题,主要涉及:
- Cell 内部 View 点击传值问题与解决方案
- RxSwift 的具体使用场景
- Subject 的一个应用场景
Cell 内部点击事件
一般对于整体一个 Cell 的点击都是通过:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 处理点击事件
}
或者是 Rx 中的:
tableView
.rx_modelSelected(ItemModel)
.subscribeNext { item in
// 处理点击事件
}
但面对复杂的 Cell ,比如:

比较好的方案就是通过 Block 回调,具体逻辑放在 ViewController 处理。那么如何配合 Rx 产生这个 Cell 内部点击的事件流呢。
可能我们最先想到的方案就是参考一般的 Observable
创建方法:
用 RxDataSource 大概是这个样子:
dataSources.configureCell = { ds, tb, ip, v in
let cell = ...
cell.plusButton.rx_tap
.map { v.count + 1 }
.subcribeNext { count in
// ...
}
.addDisposableTo(disposeBag)
cell.minusButton.rx_tap
//...
return cell
}
看起来没什么问题的样子,运行看看,点击一下,也没什么大问题。但是当我们滑动走这个 Cell ,再滑动回来时,点击一下。这个时候奇怪的事情就发生了,点击事件发生了两次。

会发生这样的情况时因为重用 Cell 的问题。当某个 Cell 重新出现时,Cell 设置的代码会重新设置一遍,这就相当于下面这段代码执行了两遍:
cell.plusButton.rx_tap
.map { v.count + 1 }
.subcribeNext { count in
// ...
}
.addDisposableTo(disposeBag)
这就相当于我们之间写了个:
cell.plusButton.rx_tap
.map { v.count + 1 }
.subcribeNext { count in
// ...
}
.addDisposableTo(disposeBag)
cell.plusButton.rx_tap
.map { v.count + 1 }
.subcribeNext { count in
// ...
}
.addDisposableTo(disposeBag)
这个就比较尴尬了。
事实上这里就是产生了一个多次订阅的问题,一个 Cell 的点击订阅了两次,自然这里的代码就执行了两次。解决起来也不是非常麻烦。有两种:
选择 Block
我认为 Subject 的存在就是为了解决不能把代码写成非常理想的流式问题。在 Cell 中我们可以这样使用:
let addProducsCountTrigger = PulishSubject<Int>()
// ...
func viewDidLoad() {
super.viewDidLoad()
addProductsCountTrigger
.subcribeNext { count in
// ...
}
// ...
}
// ...
cell.rx_tap.map { v.count + 1 } = addProductsCountTrigger.onNext
// ...
只需要在处理 Cell 的时候,表面我们要如何给商品添加数量。
我们会在本文的最后讨论一个 Subject 的应用场景。
这是我自己在用的方法,写起来还是比较容易理解的。
选择 Rx
当然了,我们仍然可以使用 Rx 的方案,唯一要注意的点就是只订阅一次。
我们有:
let cell = tableView.dequeueReusableCell("Cell")
if let cell = cell {
cell.set(...)
return cell
} else {
let cell = ...
cell.plusButton.rx_tap
// ..
}
只在生成 Cell 的时候产生这种订阅关系即可。
RxSwift 的具体应用场景
View 常驻在 ViewController 中的使用 Rx ,偶尔出现的 View (主要存在于 Cell ) 使用一般的设置点击方案。
特别是像这种 Cell ,最大问题在于 Cell 被重用,在这里建立绑定关系会比较麻烦,不如直接建立好数据与 TableView 的绑定关系。
我个人的最佳建议就是 TableView 需要建立绑定关系,而 Cell 只需要一些基本的设置就好啦。
此时我们回顾一下 RxDataSources 这个 repo ,和上面采取了一样的方案。
Subject 的一个应用场景
重新加载。
这个场景只用 Observable 是没办法完成的,看一张图:

在这里重新加载就是重新调用网络请求,总不能再写几遍代码吧?

那这里就要写无数行代码了。事实上这里出现了一个逻辑问题。因为网络错误导致的重新加载不可能出现在网络请求前面,只有进行了网络请求才能知道是否有重新加载的需求。换句话说,就是网络错误 Observable 无法创建在 网络请求 Observable 前面。
所以我们需要建立一个 reloadTrigger
的 Subject
来完成这个不可能完成事件:
let reloadTrigger = PublishSubject<Void>()
// ...
func viewDidLoad() {
super.viewDidLoad()
[reloadTrigger.asObservable(), Observable.just(())].toObservable()
.merge()
.flatMap(request) // 网络请求
.subscribeNext { [unowned self] result in
switch result in
case let value(value):
// 展示结果
case let error(error) :
Alert.show(error: errot) { // 展示一个错误信息的弹窗,并提示是否重新加载,点击重新加载则调用该 Block
self.reloadTrigger.onNext(()) // 发送一个刷新的值
}
}
.addDisposableTo(disposeBag)
// ...
}
总结
- Cell 的点击建议选择 Block 传递,或者只在建立 Cell 的时候订阅一次
- 对于状态经常改变的 View 适合建立绑定机制
- Subject 可以解决逻辑上不可逆问题