Edited at
RxSwiftDay 1

RxCocoaが提供するDriverって何?

More than 1 year has passed since last update.


はじめに

今まで以下の RxSwift に関する記事を書いてきました。


  1. オブザーバーパターンから始めるRxSwift入門

  2. RxSwift入門(2) 非同期処理してみる

  3. RxSwiftを深く理解する

  4. RxSwiftの機能カタログ

  5. Rxを使った設計をビジュアル化する

この中で RxCcoca が提供している Driver を説明してこなかったので、ここで説明しようと思います。

公式の解説 では Driver だけでなく ControlProperty, ControlEvent も含めて Units と呼んで説明されています。これらは RxCocoa で提供されていて、Rx に一般的なものではなく RxSwift 独自のものです。

RxSwift のバージョンは 3.0.1 です。


UI層とのバインドで必要なこと

iOS では UI 部品の更新はメインスレッドで行う必要があります。例えば MVP (MVVM) パターンに従って Presenter (ViewModel) が画面の状態を表していて、ViewController がそれを表示するという形の場合、Presenter (ViewModel) では以下のことを行います。


  • メインスレッドで通知

  • shareReplayLatestWhileConnected を使った Cold-Hot 変換

コードだと以下のような感じですね。

final class Presenter {

let buttonTitle: Observable<String>
// ...

init(model: Model) {
buttonTitle = model.value
.observeOn(MainScheduler.instance)
.shareReplayLatestWhileConnected()

// ...

UI 部品の値は onNext で伝えるだけで onError で伝えるものはありません。非同期処理をしてその結果を UI に伝える場合、Presenter より下でエラー処理していなかったら、UI の値に適用する前にエラー処理が必要になります。

    buttonTitle = model.value

.observeOn(MainScheduler.instance)
.catchError { _ in Observable.empty() } // エラーは無視
.shareReplayLatestWhileConnected()


DRY(Don't Repeat Yourself)!

同じことを何度もやるなら共通化したくなります。そこで Driver です。

final class Presenter {

let buttonTitle: Driver<String>
// ...

init(model: Model) {
buttonTitle = model.value
.asDriver(onErrorDriveWith: Driver.empty()) // エラーは無視

// ...

こんな感じで Driver に変換すると、


  • メインスレッドで通知

  • shareReplayLatestWhileConnected を使った Cold-Hot 変換

  • エラー処理

をやってくれます。

利用側では subscribe や bindTo でなく drive を使います。

final class ViewController: UIViewController {

// ...

override func viewDidLoad() {
super.viewDidLoad()

presenter.buttonTitle.drive(button.rx.title())
.addDisposableTo(disposeBag)

// ...

drive メソッドは Driver にしかないので、buttonTitle が Driver でなく単なる Observable だったらコンパイルエラーになります。

つまり ViewController 側で購読をする際に drive を使うことで、 Presenter (ViewModel) 側に対して Driver を使うこと・・・言い換えると


  • メインスレッドで通知

  • shareReplayLatestWhileConnected を使った Cold-Hot 変換

  • onError 通知しない

を強制することができます。


Driverへの変換メソッド

Observable を Driver に変換するメソッドは3つ用意されています。1つ目は先ほど紹介した onErrorDriveWith 引数で、エラーが発生時に指定した Driver のイベントを通知するバージョン。

observable.asDriver(onErrorDriveWith: Driver.empty())  // エラーを無視

次にエラーが発生したら指定した値を通知するバージョン。

observable.asDriver(onErrorJustReturn: [])  // エラーなら空配列を通知

あとはエラー内容に応じて処理を切り替えられるバージョン。

observable.asDriver(onErrorRecover: { error in

if error is MyError { return Driver.empty }
return Driver.just([])
});


ControlProperty

RxCocoa が提供する UI 部品のプロパティ(rx.text, rx.title など)が ControlProperty として公開されています。ObservableType と ObserverType を継承しているので、Observable として扱うこともできるし、bindTo の引数に指定することもできます。

ControlProperty として公開されているものは、当たり前ですがメインスレッドで通知されますし、エラーを発生させないので、

label.rx.text.asDriver()

で Driver に変換できます。


ControlEvent

RxCocoa が提供する UI 部品の発生させるイベント(rx.tap など)が ControlEvent として公開されています。ControlProperty と違って現在値を持たず、ObserverType でないので書き込み(外側からイベント発生)できません。


Variableの場合

Variable はエラーを発生させないので、

presenter.value.asDriver()

で Driver に変換できます。メインスレッドで通知されるようになります。


まとめ

もっと早くに説明したかったんですが、最初の「オブザーバーパターンから始めるRxSwift入門」ではまだスケジューラの話をしてなかったし、それ以降の記事でも本題から外れるし、Driver は RxSwift 本体じゃなくて RxCocoa の機能だし・・・で、別記事になりました。

ずっと途中まで書いて放置してたんですが、RxSwift の Advent Calendar 1日目が空いたままだったので、完成させて公開してみました。