will and way

ただの自分用メモを人に伝える形式で書くことでわかりやすくまとめてるはずのブログ

RxCocoaのUITableViewのbind(to: )にRegistrableを使って処理の簡略化

前提

qiita.com

Registrable型に則ればあとは型推論によるextensionの実装で済ませようというアプローチです

キャストが失敗したら?だったり、各型のIdentifierを取得する手間をextensionに閉じ込めることができるので、
シーケンスに集中することができるためコードの見通しが良くなります。

RxCocoaのBindToにこの仕組を使う

RxCocoaではbind(to)でシーケンスなElementをUITableViewのデータソースとして扱い、それを表示する拡張があります。

下記がその実装になります。

items<S: Sequence, Cell: UITableViewCell, O : ObservableType> (cellIdentifier: String, cellType: Cell.Type = Cell.self)

    public func items<S: Sequence, Cell: UITableViewCell, O : ObservableType>
        (cellIdentifier: String, cellType: Cell.Type = Cell.self)
        -> (_ source: O)
        -> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void)
        -> Disposable
        where O.E == S {
        return { source in
            return { configureCell in
                let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { (tv, i, item) in
                    let indexPath = IndexPath(item: i, section: 0)
                    let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! Cell
                    configureCell(i, item, cell)
                    return cell
                }
                return self.items(dataSource: dataSource)(source)
            }
        }
    }

しかしこのままではRegistrableにしているのにIdentifierをまた書かなければなりません。そこで、Registrableの仕組みをextensionに内包してしまおうという発送です。

デフォルトの実装に対してRegistrableを使ったインターフェイスを定義して、処理を軽くラップすれば実現することができます。

実際の変更点としては2点です

  1. cellTypeのみを引数にとるようにジェネリクスの型を調整
  2. 内部でcellIdentifierにはRegistrable.reuseIdentifireを使ってもとの実装に処理を委譲

UITableView+Rx.swift

extension Reactive where Base: UITableView {
    public func items<S: Sequence, Cell: UITableViewCell & NibRegistrable, O : ObservableType>
        (cellType: Cell.Type = Cell.self)
        -> (_ source: O)
        -> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void)
        -> Disposable
        where O.E == S {
            return self.items(cellIdentifier: cellType.reuseIdentifier, cellType: cellType.self)
    }
}

こうすることで、tableViewの処理が4行(bind() {}で書いているところ)で収まります。

サンプル(ViewController.swift#L39-L42)

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    let searchModel = GithubSearchModel()
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(SearchResultCell.self)        
        bind()
        searchModel.search(q: "Rx")
    }
}

private extension ViewController {
    func bind() {
        searchModel.searchResult.asObservable()
            .bind(to: tableView.rx.items(cellType: SearchResultCell.self)) { (row, element, cell) in
                cell.setData(data: element)
            }.disposed(by: disposeBag)
    }
}

上記の前提としては

  1. RegistrableでUITableViewCellの登録処理や取得処理をラップ
  2. Modelには検索結果をVariable<ResultData>として公開しておき、ViewController側わModelをSubscribeしておく

ただし、このbind(to:)は複数のcellのタイプを使えないのでご注意を。。。