RxSwift 再入門


RxSwift について基本を説明するとき、Observable から話を始めることが多いかと思います。一方で、RxSwift の実際の活用方法としては、データバインディングが大事になってくることが多いようです。

ここでは、まず従来どおりの Observable の話を簡単にしたあと、データバインディングの話をするという形で入門を書いてみます。

RxSwift とは

RxSwift は「非同期イベントを受け取るための枠組み」を提供してくれるライブラリです。

  • UI イベント受け取り
  • Web API レスポンス受け取り
  • データの変化の監視

RxSwift は ReactiveX ファミリーのひとつです。ReactiveX には、C#、Java など様々な言語のライブラリがあります。

コード例

(1) UIButton

button.rx.tap
    .subscribe { [unowned self] _ in
        // ボタンタップ時の処理
    }
    .disposed(by: disposeBag)

UIButton がタップされた時の処理を RxSwift で書くとこうなります。

UIKit の標準の仕組みでは Target-Action で書くところです。それと比べると、Action メソッドを用意しなくてよくなります。

(2) UITextField

textField.rx.text
    .subscribe { [unowned self] _ in
        // テキスト入力時の処理
    }
    .disposed(by: disposeBag)

UITextField で文字が入力された時の処理を RxSwift で書くとこうなります。先ほどの例とほとんど同じですね。

UIKit の標準の仕組みでは Delegate を使って書くところです。それと比べると、Delegate メソッドを実装しなくてよくなります。

(3) Notification

NotificationCenter.default.rx.notification(..)
    .subscribe { [unowned self] _ in
        // 通知受け取り時の処理
    }
    .disposed(by: disposeBag)

NotificationCenter からの通知を受け取る処理を RxSwift で書くとこうなります。やはりほとんど同じですね。

(4) URLSession

session.rx.response(request)
    .catchError {
        // エラー時の処理
    .subscribe {
        // レスポンス受け取り時の処理
    }
    .disposed(by: disposeBag)

URLSession で Web 通信をしてレスポンスを受け取る処理を RxSwift で書くとこうなります。
エラー処理が追加されている以外は、これまでの例とほとんど同じですね。

イベント処理の流れ

これまで挙げたコード例はほとんど同じ形をしていました。

  • Observable = イベントが流れてくる Sequence
    • rx.tap, rx.text, rx.notification, rx.response
  • Observable を subscribe するとイベントを受け取れる
  • dispose = subscription の破棄

これが RxSwift の基本的な使い方です。

補足:Observable に流れてくるもの

  • 流れてくるイベントは enum で定義されている
    • .next : 値
    • .error : エラー終了
    • .completed : 正常終了

UI のイベントは .next だけが流れてきて、dispose されない限り終了しません。Web 通信だと .error で終了したり .completed で終了したりします。

データバインディング

アプリ設計において、値の変化をどう伝えるかは重要な関心ごとのひとつです。

  • モデルの値の変化を UI に伝えたい
  • UI の値の変化をモデルに伝えたい

それを実現する手段はいくつか考えられますが、その手段のひとつとして RxSwift が有力です。

逆に言えば、RxSwift が注目される理由のひとつが、このデータバインディングです。ここに焦点を当てて、RxSwift に再入門してみたいと思います。

データバインディングのための手段

Relay

データバインディングのための手段として、Relay があります。

  • BehaviorRelay
  • PublishRelay

これは Observable の一種で、.next だけが流れ、終了しないというものです。

Relay は最新の RxSwift 4 で登場したものです。もとは RxJava 向けライブラリ RxRelay で導入された仕組みですが、同様のものが RxSwift にも導入されました。

Relay の使い方 : accept

let relay = BehaviorRelay<String>(value: "000")
relay.accept("111")
relay.accept("222")

accept で Relay に .next イベントを送ることができます。Observable の外側からイベントを流すことができるわけです。

なお、コードは BehaviorRelay ですが、PublishRelay でも使い方は同じです。

Relay の使い方 : bind

relay
    .bind(to: label.rx.text)

textField.rx.text
    .bind(to: relay)

イベントを受け取る側は、subscribe の代わりに bind が使えます。bind は実際には subscribe とほとんど同じです。しかし、簡潔に書け、データバインディングをしていることが分かりやすくなります。

BehaviorRelay / PublishRelay の違い

  • 初期値を持つ / 持たない
  • value プロパティで現在値が取得できる / できない
  • subscribe したとき現在値が流れる / 流れない

両者の違いは、値を保持するかしないかの違い、と言ってもいいかもしれません。データを保持したいなら BehaviorRelay が適切です。

余談ですが、RxSwift 3 では Variable というのがデータを保持する Observable でしたが deprecated になりました。代わりに BehaviorRelay が登場しました。

事例:ViewModel と ViewController の連携

単純な事例として、ViewModel と ViewController との連携を考えます。
ViewModel にデータを BehaviorRelay を使って保持するようにしてみます。

struct ViewModel {
    let username = BehaviorRelay<String>(value: "")
}

モデルの値の変化を UI に伝える

以下は、ViewModel の値が変化したとき、UILabel に表示する処理です。先ほども述べた bind を使っています。

class ViewController {
    override func viewDidLoad() {
        viewModel.username
            .bind(to: label.rx.text)
            .disposed(by: disposeBag)
    }
}

UI の値の変化をモデルに伝える

以下は逆に、UITextField に入力されたとき、ViewModel の値に反映させる処理です。やはり bind を使っています。

class ViewController {
    override func viewDidLoad() {
        textField.rx.text.orEmpty
            .bind(to: viewModel.username)
            .disposed(by: disposeBag)
    }
}

このように、RxSwift を使うとデータバインディングをシンプルに書けることが分かります。

バインディングの方向

ところで、「バインディング」という言葉からは双方向につながるような印象を受けます。つまり、一度バインドすれば、モデルの値と UI の値とが互いに連動するように思えます。

しかし、RxSwift の場合は単方向です。モデルから UI へ、UI からモデルへと、それぞれバインドする必要があります。

これは、bind は実は subscribe であることを知ると、単方向であることは分かりやすいかと思います。

(ただ、bind が双方向を連想させる言葉だとすると、命名があまり良くないのかもしれませんね)

双方向バインディングは別にいらない

ただ、実際のところ、双方向バインディングができないことはデメリットではなく、前向きに考えていいと思います。

  • 双方向だとデータの流れの In / Out が区別しづらい
  • アプリ設計として、データの流れる方向を意識できることは大事

RxSwift を使ううえでの留意点

なんでもできすぎる

  • 強力なツールのため無節操に使うとこんがらがることもある
  • アプリ設計を考えておく
    • RxSwift が何かのアプリ設計を与えてくれるわけではない
    • アプリ設計に対して、実装を楽にするツール

RxSwift が難しいと言われるのは、むしろなんでも RxSwift でやろうとしすぎて混乱しているせいかもしれません。

あまり難しく考えすぎず、シンプルに使うのが良いのではないでしょうか。

ライブラリ自体の規模が大きい

  • 様々なクラスがあって把握しづらい
  • 小さな問題を解決したいだけなら導入をためらう場合も

RxSwift 自体のバイナリサイズはそこまで気にするほどの大きさではないと思いますが、軽量なライブラリとは言いづらい感じはします。

場合によっては、より軽量な Reactive ライブラリも検討しても良いかもしれません。

上手に付き合えば便利

使いどころをちゃんと考えて使えば、とても便利なものだと思います。

補足

この記事は、先月 Mobile Act OSAKA #5 で発表した RxSwift 再入門 を文章として書き直したものです。

補足2

別途、RxSwift で難しいと言われがちなスケジューラや Hot / Cold について触れた記事を書いています。参考になれば幸いです。→ RxSwift のスケジューラ

また、RxSwift を活用したフレームワークのひとつである ReactorKit の紹介記事も書いています。こちらも参考になれば。 → ReactorKit で View をテストしやすくする