RxSwiftとは
最近流行ってる(?)FRPのフレームワークの一つです。
非同期処理のコールバック地獄をとてもシンプルに書くことができます。
また、RxKotlinなどとも似ているので、Androidとも似たようにかけるのもメリットの一つだと思います。
github.com/ReactiveX/RxSwiftもそこそこのSTAR数になっているし、実プロジェクトでもそこそこ採用されているので、さほど恐れることもないのではなかろうか
これからのプラン
- Observableの基本
- Observableの生成について
- Observableの基本的な変換〜filter, map, flatMap〜
- 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)側は、流れてきたイベントに対する挙動を登録するんだ、ということだけ意識すれば良いです。