はじめに
これは僕が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)
}
※flatMap
やmap
については後述します。
何が良いのか?
これだけでは、何も良いことがありません。
[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
が送信されると解放されます。
※その際に処理を行う場合はsubscribeCompleted
やsubscribeError
などでイベントを拾えます。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...
最後に
上級者から見れば初歩的な事ですが、僕にとってはこれらの事もわからなかったので他の誰かの役に立てば幸いです。