RxSwiftについてようやく理解できてきたのでまとめることにした(1)


RxSwiftとは

最近流行ってる(?)FRPのフレームワークの一つです。

非同期処理のコールバック地獄をとてもシンプルに書くことができます。
また、RxKotlinなどとも似ているので、Androidとも似たようにかけるのもメリットの一つだと思います。

github.com/ReactiveX/RxSwiftもそこそこのSTAR数になっているし、実プロジェクトでもそこそこ採用されているので、さほど恐れることもないのではなかろうか

これからのプラン

  1. Observableの基本
  2. Observableの生成について
  3. Observableの基本的な変換〜filter, map, flatMap〜
  4. HOT/COLDについてと、それにまつわるオペレータ 5以降:その他変換、Observableの仲間、… について

どんだけ時間取れるかわからないですが、こんな感じでかければ良いかなーと。

ターゲットは、RxSwiftの記事を読んでも画像の意味がわからない!人向けです
とはいえ画像作るの下手なので、テキストですが、具体例多めに入れながらかけたら良いなと思ってます

個人的に考えるメリット・デメリット

メリット

  • コード全体が一貫する
  • まとまった流れが見やすい、差分がわかりやすい
  • スレッドを変えやすい
  • コールバック地獄になりにくい

デメリット

  • 学習コストが高い
  • 長くなりがちで、パット見はわかりにくい

Observable入門

RxSwiftの基幹をなす概念と言ってもいいObservable。「監視できる」という名前ですが、とりあえず「イベントを流すもの」です

Observableの簡単な例を見てみましょう。これはtextFieldに入力された値に関するObservableの例です。

let observable = textField.rx.text.asObservable()
let subscription = observable
    .subscribe(onNext: { string in
        print(string)
    })

これを実行すると、textFieldに入力された値が変わるたびに、その値が print されます。
つまり、「値が更新されるたびに、登録された処理を実行」します。

ここの textField.rx.text.asObservable() が observableです。
observableが「値が更新されたというイベント」を流して、
そのイベントに対して「登録された処理」を行う(今回だと { string in print(string) }

onNext, onCompleted, onError

Observableは、以下の3種類のイベントを流します

  • onNext
  • onCompleted
  • onError

それぞれ、onNextは「値が更新された」, onCompletedは「処理が完了した」, onErrorは「エラーが発生した」というイベントを表します。

onNextは値を渡すことができますが、onCompletedは値を渡すことができません。以下のような定義ですね。(RxSwift/Event.swift)

public enum Event<Element> {
    case next(Element)
    case error(Swift.Error)
    case completed
}

3つだけですが、これら3つの組み合わせでいろいろな動作を表すことができます。

例えば、URLから何かをダウンロードするような処理は結果は一つだけなので、

通信開始 -> onNext(通信結果) -> onCompleted

インターネット接続がなかった場合は

通信開始 -> onError("no internet")

他にも例えば画像を取得する場合などで、先にサムネイルを取得した後にオリジナル画像を取得するシーンなどは、

取得開始 -> onNext(サムネイル) -> onNext(オリジナル画像) -> onCompleted

アニメーションの処理完了などのように値を渡す必要が無い場合は

アニメーション開始 -> onCompleted

のようになります。

これが、Observableです。RxSwiftの世界では、いろいろな動作をObservableとみなして、上の3つのイベントの形で扱います。

※ ルールとして、一度onCompletedやonErrorが発生するとそれ以降onNext等を呼ぶことはできなくなるので注意(複数のonCompletedが必要な場合は、それぞれをObservableにするか、onNextとして流す)

subscribe(購読)

もう一度先程のサンプルを見てみましょう

let observable = textField.rx.text.asObservable()
let subscription = observable
    .subscribe(onNext: { string in
        print(string)
    })

ここのonNextは、ObservableからonNextイベントが流れてきた時の処理として、 { string in print(string) } を登録している事になります。同様に、onCompleted, onErrorなどにも登録することができます。

let observable = textField.rx.text.asObservable()
let subscription = observable
    .subscribe(onNext: { string in
        print(string)
    }, onError: { error in
        print(error)
    }, onCompleted: { _ in
        print("completed")
    })

この登録された処理のことを observerと呼びます。名前はObservableと似ていますが、意味は逆なので注意してください!

※subscribeにはもう一つ大きな意味がありますが、HOT/COLDの章で書きます。

dispose(購読解除)

subscribe処理をいつまでも続ける訳にはいきません。これを止めることをdisposeと呼びます。クロージャなどがメモリ解放されます。

先程のコードに、

subscription.dispose()

のようにするとdisposeできます。

onCompletedやonErrorが発生するとObservableはもうイベントを発行できないので、自動的にdisposeされます。よって、明示的にdisposeする必要はありません。

disposeは適切に行わないと、メモリリークします。が、一つ一つ開放処理を書くとバグが発生しやすいです。そこで、DisposeBagを使うと半自動的に解放をしてくれるのでよく使います。

DisposeBag

disposeBagは、disposeBagオブジェクト自身が開放されるタイミングで、登録されたsubscriptionをdisposeします。

{
    let disposeBag = DisposeBag()
    Observable<Int>.never()
        .subscribe(onNext: { print($0) })
        .disposed(by: disposeBag)
}()

Observable<Int>.never() はonCompletedを流さないobservableです。これをsubscribe()しただけだといつまでたってもdisposeされませんが、disposeBagに登録しておくとこのクロージャを抜けた時にdisposeBagは開放されるので、そのタイミングで購読処理も解除されます。

これはViewControllerなどで非常に便利で、RxSwiftを使うプロジェクトではViewControllerのメンバには必ずと言ってよいほど定義して、画面に関わるsubscriptionを登録しておくことが多いです。

循環参照の防止

注意しなければならないのは、購読処理の中のclosureでself等を強参照すると解放されなくなってしまうので、 weak または unowned にしなければいけないことです。
基本的には、closureが実行される段階ではdisposeBagがdisposeされていない、すなわちselfも存在しているはずなので unowned で問題ないと思いますが、中で更に非同期処理を実行している場合などは注意が必要です。

observable
    .subscribe(onNext: { [unowned self] string in
        self.textLabel.text = string
    })
    .disposed(by: disposeBag)

また一般的に、データを流すとき、できるだけクラスではなく構造体を使うなどして、外部変数へのキャプチャや副作用はなくしたほうが良いです。

Observable<Element>

最後になりましたが、ObservableはGenericに定義されています。一つのObservableが複数回のonNextを呼んだとしても、渡される値の型は必ず同じです。
Observable<String> なら必ずonNextイベントではString型の値が渡されます。

もちろん、引数などからElementが推測される場合は型を明示する必要はありません。

複数の値を渡す場合は、

  • まとめた構造体をつくる
  • タプルにまとめる

等を行います。

まとめ

ObservableはonNext(element), onCompleted, onError(error)の3種類のイベントを流します。

イベントの受け取り(observer)側は、流れてきたイベントに対する挙動を登録するんだ、ということだけ意識すれば良いです。

続き→https://qiita.com/_ha1f/items/43b28792d27dbee7133d