RxSwiftを理解するためにオペレータを整理してみた


はじめに

RxSwiftには多くのオペレータが登場し、慣れるまでその理解が大変です。
そこで今回、RxSwift研究読本1と2を読んで登場したオペレータや、そのほか基本的なオペレータを整理してみました。
書籍の中では実装やテスト方法についても書かれているので、詳しくはそちらを読んでみると良いと思います。

オペレータ

正常系

filter

条件に合うもののみにフィルタリングする。

skip

先頭から指定した数だけイベントを無視してスキップする。

take

イベントを最初の指定した数だけに絞り、指定数に達した時点でcompletedになる。

map

新しいObservableインスタンスを返す。

flatMap

mapとの違い、型パラメータとしてObservableConvertibleTypeに準拠している必要があり、その返り値もそれに準拠している必要がある。

flatMapLatest

flatmapと違い、最新でないイベントを破棄する。

zip

複数のObservableを順番に組み合わせる。注意として一方のみの値が変っても更新されず、最新状態ではないことを認識しておく必要がある。

combineLatest

可変長でObservableを引数にとり、最後の引数にresultSelectorをとる。resultSelectorでは、各Observableの値が変わるたびに、その戻り値を次へと流れるObservableを返す。

withLatestFrom

combineLatestと違って、イベントの発行タイミングは最初のObservable の発行タイミング。

エラー系

retry

ストリームからのエラー通知時に、それをオブザーバーに通知せず、自動でストリームを再購読する。

catchError

Observableを返すことで、エラーを書き換えることができる。
エラーに応じて、自由にストリームを返したい場合や何も返したくない場合に使用する。

catchErrorJustReturn

catchErrorJustReturnは、catchErrorのラッパーとなっている。
任意の要素に書き換えたい場合に使用する。

RxSwift/Observables/Catch
extension ObservableType {
    public func catchError(_ handler: @escaping (Swift.Error) throws -> Observable<E>)
    -> Observable<E> {
    return Catch(source: asObservable(), handler: handler)
    }

    public func catchErrorJustReturn(_ element: E)
    -> Observable<E> {
       return Catch(source: asObservable(), handler: { _ in Observable.just(element) })
     }
}

ストリームの分岐

materialize

ObservableとしてたonNextイベントとonErrorイベントを一つの、Observable>として変換する。
2個のストリームを作成することで、正常系と異常系に関して、別々に購読できるようになる。

let observable = Observable<String>.create { observer in
     observer.onNext("A")
     observer.onError(TestError.test)

     return Disposables.create()
 }

_ = observable.materialize()
    .subscribe(onNext: { (event: Event<String>) in
        switch event {
        case .next(let value):
            print("value: \(value)")
        case .error(let error):
            print("error: \(error)")
        case .completed:
            break
        }
    }, onError: {
        print("onError, onError: \($0)")
    }, onCompleted: {
        print("onCompleted:")
    })

ただ正常系と異常系の分岐を、毎回書くのは面倒なので、後述するcompactMapを使用したextensionを作成すると良い。

compactMap

RxSift5.0で追加。

filterおよびmapメソッドをcompactMapメソッドでの書き換えでき、compactMapオペレータは、要素のnilをfilterしてmapしたストリームに変換してくれる。

extension ObservableType where Element: EventConvertible {

    public func elements() -> Observable<Element.Element> {
      return filter { $0.event.element != nil }
          .map { $0.event.element! }
    }

    public func errors() -> Observable<Swift.Error> {
       return filter { $0.event.error != nil }
          .map { $0.event.error! }
    }
}
extension ObservableType where Element: EventConvertible {

    public func elements() -> Observable<Element.Element> {
        return compactMap { $0.event.element }
    }

    public func errors() -> Observable<Swift.Error> {
        return compactMap { $0.event.error }
    }
}

Single等では、materializeオペレータがそのままは使えない

ObservableTypeに準拠していないSingleなどの、PrimitiveSequence型は"そのまま"ではmaterializeオペレータを使用できない。

  • PrimitiveSequence型

    • Single
      • 成功か失敗のみ送信できる
      • 完了は送信できない
      • いずれかのイベントを送信すると、disposeされる
    • Maybe
      • 成功、失敗、完了を送信することができる
      • いずれかのイベントを送信すると、disposeされる。
    • Completable
      • 完了か失敗のみ送信できる。
      • 成功は送信できない
      • いずれかのイベントを送信すると、disposeされる。

PrimitiveSequenceのStructとして、Single、Maybe、Completableは実装されている。
ジェネリクスでTraitsを切り替えることで、それらTraits(Single、Maybe、Completable)の振る舞いをするようになっている。
これらのTraitsはストリームを内部に保持したラッパーのため、materializeオペレータを使用できない。

そのためRxSwiftでは、PrimitiveSequenceのasObservable()で取り出せるように実装されている。

RxSwift/Traits/PrimitiveSequence
extension PrimitiveSequence: ObservableConvertibleType {
    /// Type of elements in sequence.
    public typealias E = Element

    /// Converts `self` to `Observable` sequence.
    ///
    /// - returns: Observable sequence that represents `self`.
    public func asObservable() -> Observable<E> {
        return source
    }
}

参考

RxSwift研究読本1 入門編
RxSwift研究読本2 エラーハンドリング編
https://qiita.com/k5n/items/e80ab6bff4bbb170122d
https://qiita.com/moaible/items/de94c574b25ea4f0ef17
https://qiita.com/k5n/items/17f845a75cce6b737d1e
https://qiita.com/crea/items/d46360e1eac709d6a632
https://qiita.com/bouzuya/items/d019c3eae5db19e395cd