Observableの変換基礎 〜filter, map, flatMap〜
3つめの記事です。ここからが本番という感じです!
1つめの記事
2つめの記事
イベントを発生させるObservableですが、これらは変換・合成などができます。この章では基本的な3つの変換、
- map
- filter
- flatMap
について書きます。
filter
let observable: Observable<Int> = Observable.from([1,2,3,4,5])
let filteredObservable: Observable<Int> = observable
.filter { value in value > 3 }
元のObservableを、onNextで渡された値について、条件がtrueの要素のみからなるObservableに変換します。onCompletedやonErrorはそのまま流します。
上の例の場合は、
onNext(4) -> onNext(5) -> onCompleted()
map
let observable: Observable<Int> = Observable.from([1,2,3,4,5])
let mappedObservable: Observable<Int> = observable
.map { value in value * 2 }
mapはonNextで流れる値を変換します。onErrorやonCompletedなどはそのまま流します。
上の例の場合は、
onNext(2) -> onNext(4) -> onNext(6) -> onNext(8) -> onNext(10) -> onCompleted()
中の実装によって、型が変わる場合もあります。
let observable: Observable<Int> = Observable.from([1,2,3,4,5])
let mappedObservable: Observable<String> = observable
.map { value in "\(value)" }
この場合、元のobservableはObservable<Int>
ですが、map
によりObservable<String>
に変換されています。
flatMap
flatMapは一番良く使うOperatorで、RxSwiftによる処理の醍醐味のようなものです!
Promiseみたいな感じで、非同期の処理をどんどんつなげることができます。
値で説明するなら、以下のような感じです。
let observable: Observable<Int> = Observable.from([1,2,3,4,5])
let flatMappedObservable: Observable<Int> = observable
.flatMap { value in Observable.from(0..<value) }
この例だと、onNextで
0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4
のあとにonCompletedが流れてきます。
区切るとわかりやすいと思いますが、それぞれの値に対してのObservableから流れる値が、連結されています。
(本当は並列処理などにより、順番が入れ替わる可能性があります)
flatMapでも、observableの型が変わる可能性があります。
中身のイベント
もちろん、中身のObservableの放つonNextは外側のobservableに流れます。
中身のObservableの放つonErrorも外側にも流れます。
よって、もし中身が一つでも失敗した場合は全体のsubscriptionが終了してしまいます。
これをしたくない場合は、以下のように中身のerrorを無視する必要があります。
// errorのときにデフォルト値を返す
.flatMap { value -> Observable<Int> in
return Observable<Int>.error(NSError())
.catchErrorJustReturn(1)
}
// errorのときに無視する
.flatMap { value -> Observable<Int> in
return Observable<Int>.error(NSError())
.catchError { error in
return error
.flatMap { _ inObservable<Int>.empty() }
}
}
また、onCompletdは大元のonCompletedのみが使われて、
flatMap内のObservableのonCompletedは無視されます。
もし中身のonCompletedで全体のsubscriptionが終了してしまっては、
2つ目の要素が無視されてしまいます。
もし外側にも流したい場合は、少し工夫する必要があります。
(記事では書きませんが、takeUntil
オペレータ などを使うと実現できます)
flatMapの利用例
例えば、ボタンがタップされたらデータを取ってきてラベルに反映する、という処理は以下のように書きます。
self.button.rx.tap
.flatMap { [weak self] event in self?.fetchData() }
.subscribe(onNext: { [weak self] data in
self?.label.text = data.title
})
.disposed(by: self.disposeBag)
※fetchData()
はObservableを返します。
リクエストが複数段必要な場合も、どんどん繋げられるのがわかるかと思います!
(flatMapの後ろにまたflatMapをつなぐ)
こうすると、コールバック地獄のようにインデントが重ならないですね!
self.button.rx.tap
.flatMap { [weak self] event in self?.fetchData() }
.flatMap { [weak self] data in self?.fetchUser(data.userId) }
.subscribe(onNext: { [weak self] user in
self?.label.text = data2.title
})
.disposed(by: self.disposeBag)
ちなみに、以下のように書いても同じですが、インデント的に見にくいので、上のように書いたほうが良いです。
(ただし、エラーとかの扱いがちょっと変わるので、必要なときはあえて下のようにネストさせて書く場合もあります。)
self.button.rx.tap
.flatMap { [weak self] event in
self?.fetchData()
.flatMap { [weak self] data in self?.fetchUser(data.userId) }
}
.subscribe(onNext: { [weak self] user in
self?.label.text = data2.title
})
.disposed(by: self.disposeBag)
オペレータ
こういうObservableを変換するもののことをオペレータと呼びます。
(ここまでで説明したのはfilter
, map
, flatMap
)
以下のように、オペレータによってObservableは変換されていきます。変換の途中でもObservableです。(Elementの型は変わることもあります)
※実際はそれぞれは新しいObservableです。
let barObservable = fooObservable
.hogeオペレータ // この時点でもobservable
.fugaオペレータ // この時点でもobservable
.piyoオペレータ // この時点でもobservable
上に上げたのは最も基礎的なオペレータで、他にも多くのオペレータがあります。
上でちらっと紹介した catchErrorJustReturn
, catchError
, takeUntil
もオペレータの一つです。
他にも、take
, merge
, withLatestFrom
, amb
など様々なオペレータがあり、
必要に応じて使い分けるとより良くかけます!
が、とりあえずこの3つが使えれば基礎はOKかなと思います!
時間があればそれらについても説明したいと思います!