Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away