RxSwift のスケジューラ

RxSwift は非同期イベントを受け取るための枠組みです（なお、[RxSwift 再入門](https://qiita.com/usamik26/items/444d6dd7386b2949c06b) という記事も書いていますので、よろしければどうぞ）。

非同期イベントと言いましたが、これは文字通り同期的でないイベントですので、現在のスレッドとは別のスレッドで発生するものです。では、RxSwift でスレッドはどのように扱われるのでしょうか？

Rx でスレッドに当たる概念がスケジューラです。

（正確にはスレッドと完全に対応する関係ではないのですが、多くの場合は1つのスケジューラに1つのスレッドを対応させます。この記事ではそのあたりの詳細は省きます）

## 余談：Rx の三要素

Rx において重要な要素が3つあります。

* Observable
* Operator
* Scheduler

スケジューラは、Rx の三要素のひとつです。

# スケジューラを使う

## Observable と Observer

まず、イベント発生元（Observable）とイベント処理（Observer）とを分けて考えます。

![Observable.png](https://qiita-image-store.s3.amazonaws.com/0/6204/c38dc8be-c5d4-87d2-f83d-f63bc242ac4f.png)

```swift
hogeObservable // ここはイベント発生元（Observable）
    .do(onNext: { _ in
        // ここもイベント発生元（Observable） 
    })
    .subscribe(onNext: { _ in
        // ここはイベント処理（Observer）
})
```

このそれぞれについて、スケジューラを指定することができます。

![Scheduler.png](https://qiita-image-store.s3.amazonaws.com/0/6204/5bf447f1-aaf3-96e7-0385-7974fdc7cb47.png)

## observeOn

`observeOn` を使うと、イベント処理のスケジューラを変えることができます。

![observeOn.png](https://qiita-image-store.s3.amazonaws.com/0/6204/3e28f51f-644a-b774-7f79-8bd195cf6e64.png)

```swift
hogeObservable
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { _ in
        // メインスレッドで動作する
})
```

例えば、イベント処理で UI 操作をしたい場合にメインスレッドで処理する、という使い方があります。

## subscribeOn

`subscribeOn` を使うと、イベント発生元のスケジューラを変えることができます。

![subscribeOn.png](https://qiita-image-store.s3.amazonaws.com/0/6204/4d64a326-5427-0ac9-27b6-fc7efa60afbf.png)

```swift
hogeObservable
    .subscribeOn(hogeScheduler)
    .do(onNext: { _ in
        // hogeScheduler で動作する
    })
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { _ in
        // メインスレッドで動作する
})
```

例えば、イベント発生元は Web API 実行スレッドやデータベースアクセススレッドで処理をする、という使い方があります。

## スレッド制御まとめ

* `subscribeOn` : イベント発生元（Observable）のスケジューラ指定
* `observeOn` : イベント処理（Observer）のスケジューラの指定

## 注意点

実は `subscribeOn` で切り替えできない場合があります。それは Observable の性質によるものです。次のセクションで詳しく述べます。

# Hot と Cold

## subscribeOn が効かない場合

Observable には、Hot なものと Cold なものとの2種類があります。

* Hot な Observable は `subscribeOn` で切り替えできない
* Cold な Observable は `subscribeOn` で切り替えできる

## 例：切り替えできないケース

```swift
let hogeRelay = BehaviorRelay(value: "A")
let hogeThread = Thread() {
    sleep(1)
    hogeRelay.accept("B")
    sleep(1)
    hogeRelay.accept("C")
}
hogeThread.start()

hogeRelay
    .subscribeOn(MainScheduler.instance)
    .do(onNext: { _ in
        // hogeThread で accept したものは hogeThread で動作（subscribeOn が効いていない）
    })
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { _ in
        // メインスレッドで動作する
})
```

このケースでは、イベント発生元は `subscribeOn` で指定したスケジューラとは無関係なスレッドで動作します。

ここでは、`BehaviorRelay` が Hot な Observable であるために、`subscribeOn` が効いていません。

## Observable の分類 : Hot / Cold

Hot と Cold の性質の違いは以下のとおりです。

* Hot
    * `subscribe` しなくてもストリームが流れる
    * `subscribeOn` で制御できない
* Cold
    * `subscribe` したらストリームが流れ出す
    * `subscribeOn` で制御できる

Hot と Cold な Observable は、具体的には以下のものです。

* Hot
    * Subject や Relay
    * Rx の外側からストリームにイベントを送れる
* Cold
    * 上記以外はだいたい Cold
    * Rx の制御下にある

## Hot / Cold についての私見

私見ですが、Cold な Observable が「普通のもの」、Hot が「特殊なもの」であると考えています。

Cold な Observable は Rx の外側によって操作されることがなく、Rx の制御下にあります。そのおかげで、Rx の仕組みである `subscribe` や `subscribeOn` で制御が可能です。それができない Hot なものは特殊な存在と感じられます。

ただ一方で、Cold なものだけでは実用上不便そうです。実際に、データバインディング目的で RxSwift を使う場合に、Subject や Relay といった Hot なものは多用します。

そういうわけなので、Cold なものとも Hot なものとも上手につきあうのが良いかと思っています。

# 補足

この記事は、先日 [Mobile Act OSAKA #6](https://mobileact.connpass.com/event/93121/) で発表した [RxSwiftのスケジューラ](https://speakerdeck.com/usamik26/rxswift-scheduler) をベースに、整理して文章として書き直したものです。