More than 1 year has passed since last update.

はじめに

これは僕がRxSwiftを学んでいる過程で覚えたことに関する覚書です。

インストール

RxSwiftのGithubからソースをダウンロードします。
中にPlaygroundが入っているので、こちらを起動できるようにします。

RxExampleをコンパイルする必要があるので、そちらをコンパイルするとPlaygroundが使えるようになります。

Observableって?

初めてReact系のプログラムを触ると、ここから分からなくなります。
僕がそうでしたし、今でも完全に把握しているとはとても言えません。(なので、この覚書を書いています。)
ここでは僕なりに把握した内容を書いておきます。(間違いなどがありましたらご指摘願います。)

Observable送るオブジェクトを複数保持しているものです。
一方でsubscribeは受信するメソッドになります。

例えば、[1,2]というオブジェクトを送って受信する場合は以下のようになります。

let sequenceFromArray = [1, 2].toObservable()

let subscription = sequenceFromArray
    .subscribe { event in
        print(event)
    }

何も面白くないプログラムですが、これが基本的な形になります。

subscribeって?

subscribeは送られたデータを受け取る処理になります。
コードは上記に書いた通りです。

ただ、上記のコードで受け取った場合はデータをアンラップできてないので実際に中身を取得する場合は以下のようになります。

let sequenceFromArray = [1, 2].toObservable()

let subscription = sequenceFromArray
    .subscribe { event in
        switch event {
        case .Next(let value):
            print(value)
        default:
            print(event)
        }
    }

もしくはsubscribeNextを使用します。

let sequenceFromArray = [1, 2].toObservable()

let subscription = sequenceFromArray
    .subscribeNext { value in
        print(value)
    }

flatMapmapについては後述します。

何が良いのか?

これだけでは、何も良いことがありません。

[1, 2].map {
    print($0)
}

と同じことしかできません。
ここで出番となるのがObservableの性質です。

Observableはデータを送信できるというところがポイントになります。
すなわち、これらの事が非同期で行えるという事がRxSwiftの利点になります。

PublishSubjectを使って見る

簡易的に使えるPublishSubjectを使ってObservableを作ってみます。

let subject = PublishSubject<String>()
// subscribeを先に行う。
subject.subscribeNext{ (value) -> Void in
    print(value)
}

// データを送信する。
subject.on(.Next("a"))
subject.on(.Next("b"))
subject.on(.Next("c"))
subject.on(.Next("d"))
// -> a,b,c,dが出力される。

データの受信部分を先に定義してから、送信するデータをあとで定義しています。
このように行う事で、処理の受信部分から定義する事ができます。

・・・という事は、上記のsubjectオブジェクトをAPIの処理に渡せば結果が返ってきた時点でsubscribeNextが発火する事になります。
例えばチャットルームの人数などをリアルタイムに切り替える場合などに使えそうです。

※実際に使う場合は[weak self]などが必要になると思いますが、本記事では割愛します。

いつ解放されるの?

もう一つ重要なのがsubjectしている関数がいつ解放されるかというところです。

subject.onCompleted()
subject.onError(ErrorType.OriginalError)

送信側で上記のCompletedもしくはErrorが送信されると解放されます。
※その際に処理を行う場合はsubscribeCompletedsubscribeErrorなどでイベントを拾えます。subscribeメソッドで全てを一括定義する事もできます。

受信側ではsubjectオブジェクトを保持してdisposeを行う事でsubscribeを止める事ができます。

subject.dispose()

自動解放はできないの?

完全な自動解放はできないのですが、変数のスコープを利用した解放ならできます。
例えば以下のようなコードになります。

class Example {
    let disposeBag = DisposeBag()

    func subject() -> PublishSubject<String> {
        let subject = PublishSubject<String>()
        subject.subscribeNext({ (value) -> Void in
            print(value)
        }).addDisposableTo(disposeBag)
        return subject
    }
}

disposeBagという変数にDisposeBagオブジェクトを保持させてaddDisposableToでオブジェクトを付与させておけば、disposeBagが解放されたタイミング(classが破棄されるなど)でsubscribeが止まります。

Observable.create

PublishSubjectを使っても汎用的に使えますが、subjectオブジェクトを持っているところ全てでデータを送信できてしまうのでコードが読みにくくなります。
また、下手に弄られると予期せぬところでデータを送信されてしまうため堅牢なプログラムを作る事が困難になります。

その場合はObservable.createであらかじめObservableを作って渡したほうが堅牢になります。

class Api {
    func request() -> Observable<Int> {
        Observable<Int>.create {obj in
            // APIに対してリクエストして、結果がIntで返ってくるとする。
            Server.request { (value) in
                obj.onNext(value)
            }
            // createの返却値は「おまじない」として以下のように書いておけば、大抵問題ないはず。
            return NopDisposable.instance
        }
    }
}

class Receiver {
    func hoge() {
        Api.request().subscribeNext({ (value) -> Void in
            print(value)
        })
    }
}

このようにObservable<Int>.createを使ってObservableを返す事で、処理内容を隠蔽して返す事ができます。

flatMap

また、処理の途中で内容を変えたい場合はflatMapが便利です。

Api.request().flatMap {  (value) -> Observable<(Int, String)>
    return Observable.just((value, "str: \(value)"))
}.subscribeNext({ (value, str) -> Void in
    print(value, str)
}

処理結果に対して文字列の結果もタプルで返す例です。
flatMap内でjsonの解析や別のAPIを呼び出す事もできます。(非同期にする場合は再度Observable.createを作成して返してあげます。)

その他

それ以外にも以下のようなものが標準で用意されているので、使いこなすととても便利です。

  • Observable.combineLatest両方の結果が返ってくるまで待機している。両方そろった後に片方の結果が返ってきても呼び出される。
  • Observable.zip両方の結果が返ってくるまで待機している。両方そろった後に片方の結果が返ってきても呼び出されず、もう一方の結果を待つ。
  • distinctUntilChanged結果が変わらない場合は呼び出さないようにする。
  • .debounce(0.7, scheduler: MainScheduler)一定時間毎に結果を取得する。(インクリメントサーチなどで使える。)

etc...

最後に

上級者から見れば初歩的な事ですが、僕にとってはこれらの事もわからなかったので他の誰かの役に立てば幸いです。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.