使用 RXSwift 构建 UITableView

7,967
Swift Version:5.0 
RXSwift Version:5.0

本文介绍的是如何使用 RX 构建 UITableView,需对 Swift 有基础的了解。共 1100 字,阅读需 10 min。

先说一下构建的主要步骤,这样大家能更好的理解代码。

使用 RXSwift 构建 UITableView 的步骤

  1. 构建 Observable 类型的数据源
  2. 将数据源与 tableView 绑定
  3. 绑定 tableView 的事件(如:cell 的点击事件)
  4. 设置 tableView Delegate/DataSource 的代理方法(根据需求,非必要)

准备工作

在我们开始之前,我们需要在项目中集成 RXSwift,具体步骤可参见 RXSwift。成功集成之后,我们需要在要使用 RXSwift 的文件中导入它:

import RxSwift

接着,我们要创建一个 DisposeBag 类型的全局变量(一定要是全局变量):

let disposeBag = DisposeBag()

关于 DisposeBag 的作用: DisposeBag 对于 RX 相当于 ARC 对于 iOS,即它是 RX 管理对象内存的一种方式。

最后,将需要使用的 cell 进行注册:

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

OK,到现在为止,我们已经完成了所有的准备工作。接下来,我们可以根据上面的步骤来构建 tableView 了。

构建 Observable 类型的数据源

首先,我们需要构建 Observable 类型的数据源。至于为什么要构建 Observable 类型的数据,我们可以从 RXSwift 的文档上一探究竟。

它的文档上是这么写的:Every Observable sequence is just a sequence. The key advantage for an Observable vs Swift's Sequence is that it can also receive elements asynchronously。这句话的意思是说,可观察序列和 Swift 的原生序列本质上是一样的,但它们有一个最主要的不同,那就是可观察序列可以异步接受元素

通过下面的代码创建 Observable 类型的数据源:

let texts = ["Objective-C", "Swift", "RXSwift"]
let textsObservable = Observable.from(optional: texts)

此时,你用 option 键查看 textsObservable 属性的类型,应该显示的是 Observable<String>

数据源类型

可供使用的数据源已经构建完成,接下来需要将数据源与 tableView 绑定。

将数据源与 tableView 绑定

通过下面代码进行绑定(不要忘记最后的 .disposed(by: disposeBag) ):

textsObservable.bind(to: tableView.rx
    .items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, text, cell) in
        cell.textLabel?.text = text
    }
    .disposed(by: disposeBag)

在绑定方法的闭包中,我们需要定义三个变量,三个变量分别有以下含义:

  • 第一个变量为当前cell所处的行数,即:indexPath.row
  • 第二个变量为可观察序列在当前行数索引的元素,即:texts[row]
  • 第三个变量为当前cell

闭包底层实现

绑定 tableView 的事件

通过下面代码进行绑定(cell 的点击事件):

tableView.rx.itemSelected.bind { (indexPath) in
        print(indexPath)
    }
    .disposed(by: disposeBag)

同样,不要忘记最后的 disposed 。

到这里,我们就完成了通过 RX 构建一个简单的 tableView 。如果你对 tableView 还有一些自定义的需求,可通过第四步骤完成。

设置 tableView Delegate/DataSource 的代理方法

通过下面的代码设置代理:

tableView.rx.setDelegate(self).disposed(by: disposeBag)
tableView.rx.setDataSource(self).disposed(by: disposeBag)

然后,你就可以实现相关的代理方法来进行自定义了,以设置高度举例:

extension ALGExploreDetailVC: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }
}

注意:如果你不需要使用 UITableViewDelegate/UITableViewDataSource 的委托方法的话,你是可以不写的。

// 不使用 RX 的话,需要实现代理方法
tableView.delegate = self
tableView.dataSource = self
extension ViewController: UITableViewDelegate, UITableViewDataSource {
    ....
}

// 使用 RX 
// 绑定即可,不需写上面的代码
textsObservable.bind(to: tableView.rx
    .items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, text, cell) in
        cell.textLabel?.text = "\(text)"
    }
    .disposed(by: disposeBag)

如果使用 RX 绑定了 tableView ,再使用下面的代码就不对了,我们需使用 RX 设置代理的方法。

tableView.delegate = self

总结

  • 使用 RX 可以使代码简洁,易读
  • 使用 RX 语句后需调用 .disposed(by: disposeBag) ,释放内存

完整代码

import UIKit
import RxSwift

class TestViewController: UIViewController {
    var tableView = UITableView(frame: .zero)
    let kCellHeight: CGFloat = 40
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.white
        setupSubviews()
    }
}

extension TestViewController {
    func setupSubviews() {
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        view.addSubview(tableView)
        
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.safeEdges(to: view)
        //1.创建可观察数据源
        let texts = ["Objective-C", "Swift", "RXSwift"]
        let textsObservable = Observable.from(optional: texts)
        //2. 将数据源与 tableView 绑定
        textsObservable.bind(to: tableView.rx
            .items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, text, cell) in
                cell.textLabel?.text = text
            }
            .disposed(by: disposeBag)
        //3. 绑定 tableView 的事件
        tableView.rx.itemSelected.bind { (indexPath) in
                print(indexPath)
            }
            .disposed(by: disposeBag)
        
        //4. 设置 tableView Delegate/DataSource 的代理方法
        tableView.rx.setDelegate(self).disposed(by: disposeBag)
    }
}

extension TestViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }
}

参考