[RxSwift]Operatorの使い所などを記載していく


Operatorを探すときは

「こういう動きをさせたい! 何か良いオペレータはないか?」と思った時は、RxJS Marblesのサイトや、RxMarblesのアプリなどを使用し、マーブルダイアグラムを見てみて探す。

Operator

Operatorの詳細な定義はソースコードやその他解説などを見て欲しい。ここでは、実装していく上で「こういう動きがしたい」という時にどのオペレータが役だったか書いていく。

Observableと別のObservableを合体させる系

merge

複数のObservableのどれかがイベントを流したらイベントをストリームに流す。

    private var dismissDriver: Driver<Void>
    {
        //×ボタンか閉じるボタンのどちらかがタップされたらストリームを流す
        return Observable.of(
                tappedXButtonRelay,
                tappedCloseButtonRelay
            ).merge()
            .asDriver(onErrorDriveWith: Driver.never())
    }

combineLatest

複数のObservableの最新の要素を掛け合わせてイベントをストリームに流す。

loadHomeTopMenuDataからストリームが流れてきたら、その要素と、self.loadHomeWidget()の最新の要素をcombineLatestによって組み合わせる(ここでは、二つをタプルとしている)。タプルとして組み合わせた要素を、map内で使っているのが確認できる。

    func loadHomeData() -> Observable<HomeModel> {
        return loadHomeTopMenuData()
            .flatMapLatest({ Observable.combineLatest(Observable.just($0), self.loadHomeWidget()) })
            .map { topMenuModels, widgetModels -> HomeModel in
                HomeTranslator.generate(topMenuModels: topMenuModels, widgetModels: widgetModels)
        }
    }

zip

複数のObservableの全ての要素が揃ったら、初めてストリームを流す。

例えば、通信で取ってきたアプリの最新バージョンと、ローカルのアプリの現行バージョンの情報が揃ってから、それらを比較し、アプリをアップデートするかどうか決めるなど。

func checkSettingAndUpdateMaster() -> Observable<CheckSettingResult> {
        return Observable.zip(settingRepository.fetchSettingData().asObservable(), settingRepository.fetchLocalSettingInfo())
            .flatMapLatest { [unowned self] settingEntity, localSettingInfo -> Observable<CheckSettingResult> in

                //通信で取ってきたバージョンとローカルに記録されたバージョンを比較し、必要ならアップデートを行う
        }
    }

あるObservableにイベントが流れたら、イベントの値を変更する

flatMap系統

複数のObservableが関わるという点で、上に記載の合体系と似てはいる。
が、上記の合体系は複数のObservable両方が考慮に入れられイベントを流すか決めるのに対し、flatMap系列はあくまで一つのObservableが主となる。これらは、構文の違いにも現れている。

Observable.of(
   ObservableA, 
   ObservableB
).merge {
//...何かの処理
}

ObservableA.flatMap { elementA -> Observable<B> in 
if someCondition {
   return ObservableB
} else {
   return Observable.empty()
}

flatMapの呼び出し主であるObservableAにイベントが流れた際に起動する。その点ではmapオペレータなどと同じだが、特徴としては返り値としてObservableを返すという事(上の例ではObservableが返り値となっている)。

もしflatMapが無ければ、「Observableが流れたタイミングでObservableのイベントを流したい!」という時、以下のようにネストしたsubscribeをしなければいけなくなってしまう。

ObservableA.map { elementA -> B in 
if someCondition {
   ObservableB
      .subscribe(onNext: { B in 
         //ObservableBの要素を使った処理
       }
}

参考 https://qiita.com/yimajo/items/393ec9b3b445ec170ce4

scan

主にあるObservableの以前の値と今流れてきた値の両方が必要な時(両方を比べた上でどんな動作をするか決めたいとき)に利用できる。言い換えれば、単純に今流れてきた値だけではどういう動作をしたいかを決められず、一つ前の値を参照したいような時に使う。

example.swift
extension ObservableType {

    func withPrevious(startWith first: E) -> Observable<(E, E)> {
        return scan((first, first)) { ($0.1, $1) }
    }
}

Observableを生成する系

from

Swiftの通常のオブジェクトをObservableとして扱う際使える。特にSwiftでは、array型やSequenceプロトコルに準拠した型を渡すと、それらの中身の要素を一つ一つonNextで流してくれる。

例えば、配列内の要素を一個一個しか取らないメソッドなどがあるときに、配列を分解して渡す目的で使うことができる。

//データ追加
Observable.from(entities) //[Entity]型の配列をfromにわたし、Entity型の要素が順番に流れるようにする
    .flatMap { entity -> Observable<Void> in 
         RealmUtil.add(entity: entity) //Entity型を引数としてとる関数
     } 
        .asObservable()

of

何か静的な固定データ(実行時に替わらないようなデータ)をコード上にベタ書きし、その内容をObservableとして返したい時に使っていた。

    func loadData() -> Observable<[TutorialModel]> {
        return Observable.of([
            TutorialModel(imageName: R.image.tutorial_1.name),

            TutorialModel(imageName: R.image.tutorial_2.name),

            TutorialModel(imageName: R.image.tutorial_3.name)
        ])
    }

複数の要素を指定すると、それらを別々のEventとして順番に流す(fromと同じ)。

.swift
Observable.of(1, 2, 3)
    .subscribe(onNext: {
        print($0)
    })
// 出力結果は、
// 1
// 2
// 3

また、mergeなどの合体系のObservableを使用する時に一緒に使用していた。こういったofの使用例はRxMarblesなどを見ると書いてあるのでofも一緒に使うということを丸暗記する必要はない。

    private var dismissDriver: Driver<Void>
    {
        return Observable.of(
                tappedXButtonRelay,
                tappedCloseButtonRelay
            ).merge()
            .asDriver(onErrorDriveWith: Driver.never())
    }

empty()

emptyは直ちにonCompletedにイベントを流して終了する(onNextにもonErrorにもこない)。
直ちにsubscribeが終了してしまう事から、これ単体で使う時は毎回observableを生成するのが通常だろう。

以下の例では、SingleでAPI呼び出しをする際、条件が整わずAPIが呼べなかった場合は何もせず終了するという意味でempty()を呼んでいる。この変数を参照するたびに新たなSingleまたはemptyのSingleが生成され、APIの再呼び出しが試みられる。

    var detailInfoSingle: Single<DetailInfo>
    {
        if let token = AppDataManager.decodeAppToken()
        {
            let targetType = GetMemberDetailTargetType(token: token)

            return moyaProvider.shared.request(targetType)
        }
        else
        {
            return Observable.empty().asSingle() //APIを呼べなかった時はemptyで何もせず終了する
        }
    }

また、流れてきた値がoptionalだった場合、unwrapする目的でも使える。

参考:Observableをnilでフィルタしてアンラップする

observable //型はObservable<String?>
    .flatMap { string -> Observable<String> in
        if let string = string {
            return Observable.just(string)
        } else {
            return Observable.empty()
        }
   }

nilだった場合何もしないので、nilは返ってこないという事になる。

just()

要素を一つイベントで流し、直ちにcompleteするオペレータ。
何らかのメソッドを読んだ際、その都度一回だけストリームを流し、flatMapなどを使って他のObservableからデータを引っ張ってきて、その一回限り何かをするなどでよく使用していた。

    func checkNotificationSettings() {
        Observable.just(Void())
            .delay(.seconds(1), scheduler: MainScheduler.instance)
            .flatMap { [weak self] _ -> Observable<UNAuthorizationStatus> in
                self!.getStatusOfNotificationSettings()
        }
        .subscribe(onNext: { [weak self] status in
            guard let self = self else { return }

            if status == .notDetermined { // 未回答
                Logger.debug("Not Determined user's response to permission for push notification yet.")
                self.checkNotificationSettings()//確認を繰り返す
            } else { // 許諾に対して回答済
                Logger.debug("Determined user's response to permission for push notification already.")
               // do something
            }
        }).disposed(by: disposeBag)
    }