はじめに
昔あったなーという話をちょっとずつ投稿してみるテスト。
あと、RxSwiftを触っている人は知ってることだと思いますし、そうではない人はあまり意識しない、微妙にニッチというか隙間な話だと思います。
Observable.justとObservable.create
Observableを生成してストリームを流す時、Observable.justとかObservable.createを利用します。
例えば下記のように、subscribeすると「1」が流れるObservableがあります。
// 1. justに1を入れる
Observable.just(1)
// 2. 空の値をjustに流し、mapで1を流す
Observable.just().map {1}
// 3. 1を流すObservableを生成する
Observable.create { observer in
observer.on(.next(1))
observer.on(.completed)
return Disposables.create()
}
}
// subscribe時は、いずれも1が流れます
この3つの方法ですが、いずれもsubscribe時は1を流してくれます。
結果が同じなら気にしなくてもいいのでは?と思いますが、宣言部分とsubscribe部分を分けた上で、複数回subscribeをした時に少し挙動が違ってくることがあります。
複数回subscribeをした時の挙動の違い
例えば宣言を変数に格納し、数回subscribeをした場合に違いが出てきます。
下記にあるように、ランダムな数字を流すコードを実行をしてみるとわかるのですが、こんな違いが出てきます。
サンプル
https://github.com/kirou/RxSwiftQiita2
// 宣言
let testRand1 = rand1()
let testRand2 = rand2()
let testRand3 = rand3()
// testRand1のsubscribe 1回目
testRand1
.subscribe(onNext: { dump($0) })
.addDisposableTo(disposeBag)
// testRand1のsubscribe 2回目
testRand1
.subscribe(onNext: { dump($0) })
.addDisposableTo(disposeBag)
// testRand2のsubscribe 1回目
testRand2
.subscribe(onNext: { dump($0) })
.addDisposableTo(disposeBag)
// testRand2のsubscribe 2回目
testRand2
.subscribe(onNext: { dump($0) })
.addDisposableTo(disposeBag)
// testRand3のsubscribe 1回目
testRand3
.subscribe(onNext: { dump($0) })
.addDisposableTo(disposeBag)
// testRand3のsubscribe 2回目
testRand3
.subscribe(onNext: { dump($0) })
.addDisposableTo(disposeBag)
/*
* Observable外で、arc4random_uniformを実行する
*/
func rand1() -> Observable<UInt32> {
let rand = arc4random_uniform(10)
return Observable.just(rand)
}
/*
* 空の値がmapに伝わり、map内でarc4random_uniformを実行し、値を流す
*/
func rand2() -> Observable<UInt32> {
return Observable.just()
.map { arc4random_uniform(10) }
}
/*
* 空の値がmapに伝わり、map内でarc4random_uniformを実行し値を流す
*/
func rand3() -> Observable<UInt32> {
return Observable.create { observer in
observer.on(.next(arc4random_uniform(10)))
observer.on(.completed)
return Disposables.create()
}
}
- ストリームに流れた値 (実際に実行した時に落ちた値)
回数 | testRand1 | testRand2 | testRand3 |
---|---|---|---|
1回目 | 4 | 5 | 3 |
2回目 | 4 | 7 | 2 |
実行してもらうとわかるのですが、testRand1は何回実行しても同じ値が流れ、testRand2と3は毎回変わった値が流れます。
この違いは何かと言うと、宣言時の動作に違いがあるからです。
testRand1では、乱数の生成処理がObservable外にあるため、宣言時の段階で乱数を生成されます。それをObservable.justに格納しています。
subscribe時はその格納された値が流れるため、結果が同じです。
(もちろん、再度宣言をし直したら値は変わります。あと、subscribe時はObservable内の要素を観測するため、subscribeごとに乱数生成の処理をすることはありません)
testRand2と3は、Observableで囲われているため、宣言の段階で処理が動くことはありません。subscribeされた時に実行されるので、subscribeされるたびに乱数が生成されます。
(これも下記のよう、shareReplay(1)をすれば何度も乱数生成の処理が動くことはありません。Hot変換ですね)
// shareReplayなどのHot変換がある場合は
// testRand1のように、何度subscribeしても初回に乱数を生成した時の値が流れてくる
func rand4() -> Observable<UInt32> {
return Observable.create { observer in
observer.on(.next(arc4random_uniform(10)))
observer.on(.completed)
return Disposables.create()
}
.shareReplay(1)
}
こうしてみると、testRand2とtestRand3はほぼ同じですが、testRand2はjust()を挟んでいるので無駄処理かなあという感じです。
まとめ
まとめるとこんな感じになります。
工数 | testRand1 | testRand2 | testRand3 |
---|---|---|---|
宣言時 | Observable外にあるarc4random_uniformを実行する | 何もしない | 何もしない |
subscribe | Observable内の処理が実行される(宣言時に実行した値が流れるだけ) | Observable内の処理が実行される | Observable内の処理が実行される |
複数回実行した時 | Observable内の処理が実行される (宣言時に実行した値が流れるだけ) | Observable内の処理が実行される | Observable内の処理が実行される |
今年の春あたりはこのあたりふわふわしていたので、Observable外の処理が宣言時に実行されてしまい、動作がおかしいことがありました。
このことに気づいてからは、ああ、だからみんなObservable.createで囲ってるんだなと納得した記憶があります。
(ちゃんとRxを理解していない&そもそもalamofireのrequest().requestしなければよかったという話でもありますが。。)