RxSwiftで双方向データバインディング

  • 71
    Like
  • 2
    Comment
More than 1 year has passed since last update.

 FunctionalReactiveProgramming(FRP)の勉強中です。
まだ正直なところ『なるほど,分からん』という状態ですが
RxSwiftをおっかなびっくり触ってみた結果を整理しようと思います。
(触ってみたら動いたレベルなので他に良い使い方があるかも......です)

1. RxSwiftとは?

 ReactiveX(Reactive Extension)という,FRPを実現するためのライブラリのSwift実装です。
Swift実装以外にもJavaScriptやJava,C#など様々な言語で実装されたライブラリがあるようです。
(AndroidだとRxAndroidとかあるみたいですね)

RxSwift

2. どんなことが出来るの?

 分かりやすいポイント(というか自分が現状で理解できた範囲では)
「データバインド」「Promiseパターン」を簡単に実現できるようになります。
アプリの実装レベルで言うと,画面描画処理が楽になる(データの変更がViewに伝搬する)ことと
コールバック地獄になりがちな非同期処理をスッキリ書けるようになることが恩恵ですね。
※今回はデータバインドだけについて書いてみます。

3. 導入するには?

 CocoaPods先生に頼りましょう。

Podfile
# Podfile
use_frameworks!
pod 'RxSwift', '~> 2.0.0-beta'
pod 'RxCocoa', '~> 2.0.0-beta'
pod 'RxBlocking', '~> 2.0.0-beta'

 pod installで上記3つのライブラリが導入されます。

4. 単方向バインディングをやってみる

 UITextField→ViewModelにバインドしてみます。

ViewController
import UIKit
import RxSwift
import RxCocoa
import RxBlocking

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    let viewModel = ViewModel()
    let bag       = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        bindViewAndModel()
    }

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

 UITextFieldが変わる度に,subscriveNextで渡したClosureが作動します。
結果,ViewModelの変数textが同期して更新される...といった形になります。

5. 双方向バインディングをやってみる

 今度は、UITextField←→ViewModelの双方向バインディングです。
ちょっと調べてみるとRxSwiftの中の人がこんなことを仰ってました。

two-way binding

Two way binding are just two unidirectional bindings :).

 ただの単方向バインディング2つを合わせりゃ双方向バインディングだよ,と。
一発で双方向バインドする方法はないから,交互にバインドするしかない……?
というわけで試してみました。

 今度はViewModel側からもBindするので,ちょっとViewModelを書き換えます。

ViewModel
import Foundation
import RxSwift

class ViewModel {

    var text:Variable<String> = Variable<String>("森伊蔵とキッコロ")

    init() {
        // 初期化
    }
}

 String型ではなく,RxSwiftが提供するVariable型で定義します。
Variable型を使うことで,バインド用の関数が使えるようになります。
で,ViewController側にもViewModel→ViewControllerのバインド設定を追加。

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

 ViewModel→UITextFieldへのバインドは,bindTo関数だと上手くいくようです。
コードからViewModelの値を変えてあげると連動してUITextFieldの値も更新されます。