スマートデバイスアプリ開発のあれやこれや

RxSwiftでデータバインド -補足編-

 このエントリは、Qiitaに投稿した記事の補足です。

qiita.com

 上記の記事では、RxSwiftを使った双方向データバインドの
実現方法をまとめていますが、データバインドの解除方法についても
少し整理して書いておきたいと思います。

1. データバインドを解除しないと困るケース

 自分が遭遇した困るケースは、UITableViewとのデータバインド......。
UITableViewの中で扱われるUITableViewCellのインスタンスは使い回しされます。
例えば、7行表示のテーブルでは、Cellのインスタンスは7つだけ作成され
画面スクロール時も当該インスタンスのプロパティを更新する動きになります。


 そのため、CellとViewModelのインスタンス(Array)をバインドすると
ViewModelとControllerの1対1のバインド関係が崩壊してしまい
ViewModelを更新したタイミングで意図しないCellが書き換えられたりします。
1行目のCellとバインドされたViewModelを更新すると1行目のCellを流用した
○行目のCellがなぜか更新されてしまう......なんて感じですね。


 対策として、非表示になった行については
データバインドを解除してやることでViewModelとControllerのバインド
関係の整合性を保ちます。この方法を採ることで、データバインドされている
Cellは画面に表示されている部分だけ......という状況を作っています。

2. データバインド解除する方法

 簡単です。
データバインドの処理を書いたときのことを思い出します。

/**
 データバインド設定
 */
private func bindViewAndModel() {
    textField.rx_text.subscribeNext { [unowned self] text in
        self.viewModel.text = text
    }.addDisposableTo(bag)
}   


 上記コードのsubscribeNext関数はDisposable型インスタンスを返却します。
(返却されたインスタンスを即座にDisposableBagに突っ込んでますが......)


 このDisposable型インスタンスとは何か?という点がポイントになります。
Disposableは”バインドしたよ!”という保証書だと理解すると簡単です。
RxSwiftではDisposable(保証書)が有効である限りデータバインドを行います。
逆にいうとDisposableを失効させればバインドを解除できます。
Disposableクラスは、dispose関数を提供しているので実行しましょう。

/**
 データバインド設定
 */
private func bindViewAndModel() {
    let disposable = textField.rx_text.subscribeNext { [unowned self] text in
        self.viewModel.text = text
    }
    // データバインド解除
    disposable.dispose()
}   


 ね?簡単でしょう。

3. DisposeBagとは何か?

 Disposableが何か分かると、DisposeBagの役目も分かります。

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    let viewModel = ViewModel()
    let bag       = DisposeBag()
    // 中略
}


 Disposableインスタンスを保持するのがDisposeBagです。
その役目は即ち


 ”deinitのタイミングで保持している全Disposableを失効させる


ことです。
上記のコードで言うと
画面破棄のタイミングで自動的にデータバインドが解除されます。

 明示的にデータバインド解除を行わない限りは
DisposeBagを活用するのがベストプラクティスになると思います。
(解除を忘れると怖いですし......)