8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Observable.justとObservable.createのストリームを流す時の微妙な動作の違い

Posted at

はじめに

昔あったなーという話をちょっとずつ投稿してみるテスト。
あと、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しなければよかったという話でもありますが。。)

8
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?