[RxSwift] shareReplayをちゃんと書いてお行儀良くストリームを購読しよう

RxSwiftのサンプルコードを見てみると、頻繁にshareReplayというオペレーターが登場することに気づくと思います。しかし、このshareReplayを書かなくともコードは動くし、ホントにこれ必要なの？てか、コイツは何をしてるの？という疑問が湧いたので詳しく調べてみました。

# shareReplayがないとき
まずは、shareReplayがなぜ必要なのか考えるためにRxSwiftのExampleを多少シンプルにした下記の様な画面の実装について考えてみます。

![shareReplay_1.png](https://qiita-image-store.s3.amazonaws.com/0/37365/4a8eae7e-ec8c-2674-5914-ea49f18dbe13.png)

↓のような感じで3つのIBOutletで接続していて、

```swift
    @IBOutlet weak var usernameOutlet: UITextField! // ユーザー名を入力するTextView
    @IBOutlet weak var usernameValidOutlet: UILabel! // 赤い文字で警告を表示するUILabel
    @IBOutlet weak var registerButtonOutlet: UIButton! // 登録ボタン
```

入力テキストが5文字以上かどうかをValidateして

* 5文字以上入れてねという警告を表示するUILabel(usernameValidOutlet)をhiddenにするかどうか
* 登録(registerButtonOutlet)ボタンをenableにするかどうか

のハンドリングを実施したい、という要件です。これをRxSwiftを使って実装すると

```swift
let usernameValid = usernameOutlet.rx_text
    .map{ text -> Bool in
        print("[execute validation]")
        return text.characters.count >= minimalUsernameLength
    }

usernameValid
    .bindTo(usernameValidOutlet.rx_hidden)
    .addDisposableTo(disposeBag)

usernameValid
    .bindTo(registerButtonOutlet.rx_enabled)
    .addDisposableTo(disposeBag)
```

みたいな感じになると思います。printログ出すために本来は不要な処理を若干書いてますが。。。さて、この実装の問題点はどこでしょうか？実は、この実装では<b>テキストを1文字入力するたびに、[execute validation]が２回出力されてしまう</b>という問題があります。

これは、`usernameValid`を`usernameValidOutlet.rx_hidden`と`registerButton.rx_enable`の2つのプロパティに対してbindし購読しているためです。今回は2つだけですが、それでも無駄な処理が走ってしまっているのは気になります。さらに、今回はmapして中で簡単なvalidationを実施してるだけですが、もしもこれが<b>IOを伴う処理だったり、通信を伴う処理だったら</b>と考えるとあまり良い気持ちにはなりません。

# shareReplayがあるとき
そこで、shareReplayの出番です！usernameValidのコードを修正して、最後にshareReplayを呼び出すよう変更してみましょう。

```swift
let usernameValid = usernameOutlet.rx_text
    .map{ text -> Bool in
        print("[execute validation]")
        return text.characters.count >= minimalUsernameLength
    }
    .shareReplay(1) // 最後にshareReplayを呼び出す
```

この修正を加えた上で文字を入力すると、<b>テキストを1文字入力すると1度だけ[execute validation]が出力される</b>ようになっているはずです。

# なぜ？
なぜ、shareReplayを追加すると、[execute validation]の出力が1回だけになったのでしょうか？それを知るためには、実は、`replay`と`refCount`という2つのオペレーターについて知る必要があります。

なぜなら`shareReplay(1)`は、内部的には`replay(1).refCount`を呼び出してるだけのショートカットだからです(厳密には頻繁に使われるであろう<引数が1のとき>に限っては内部で最適化した別の処理を実行しているみたいです）。つまり、さきほどのコードを下記の様に書いても実行される処理は全く同じです。

```swift
let usernameValid = usernameOutlet.rx_text
    .map{ text -> Bool in
        print("[execute validation]")
        return text.characters.count >= minimalUsernameLength
    }
    .replay(1)
    .refCount()
```

ということは、`replay`と`refCount`が何者かわかれば、`shareReplay`が何かもわかる、というわけですね。こいつらは何者なのでしょうか？

## ConnectableObservableへの変換
ここで`replay`が果たしている大きな役割の一つが、`ConnectableObservable`の変換です。`replay(1)`により、`usernameValid`は`ConnectableObservable`と呼ばれる`Observable`に変換され、通常の`Observable`とは少し異なる挙動をするようになります。

`ConnectableObservable`と通常の`Observable`の大きな違いは、ストリームの生成方法とタイミングです。

通常の`Observable`の場合は<b>subscribeした時点でObserver毎に計算リソースが割り当てられ、subscribeしているObserverが複数存在する場合は別々に値を通知する</b>という挙動になっています。つまり、最初の例のように2つのオブジェクトにbindしsubscribeすると、<b>別々の2本のストリームが生成される</b>ということです。subscribe(bindTo)した数だけmap内の処理が走ってしまう、という意図せぬ動きになっていたのは、この性質のためです。

一方`ConnectableObservable`は<b>subscribeしているObserverが複数いたとしても、全てのObserver間で共有の計算リソースが割り当てられ、同時に値を通知する</b>という挙動になります。わかりやすく言えば、<b>1本のストリームが分岐してそれぞれのsubscribe元に情報を届ける</b>、というイメージです。つまり最初の例で言うならば、複数箇所でsubscribe(bindTo)されていてもmap内の処理は1文字入力したときには1度だけ走るようになる、というわけです。

なお、同じ様に`ConnectableObservable`に変換するオペレーターとしては`publish`というものもあります。`replay`と`publish`の違いは直近のイベント(後述するconnect()を実行する前に既に発行されてしまったイベント)が再送されるかどうか、です。

>publish との違いは、publish が Subject を使うのに対し replay が ReplaySubject を使う点です。subscribe したタイミングで指定した個数の直近 に流れたデータが再度流 (replay) されます。
[RxJS の Operators (11) - Connectable Observable Operators](http://qiita.com/bouzuya/items/c93e522b18b752defb77) より

さて、これで意図しない処理が走ることもなくなったしメデタシメデタシ、、、ではありません。`refCount()`をコメントアウトしてみればわかりますが、`replay(1)`で終わった状態だとsubscribeしているにもかかわらずストリームが何も流れてきません。これは、なぜでしょうか？

```swift
let usernameValid = usernameOutlet.rx_text
    .map{ text -> Bool in
        print("[execute validation]")
        return text.characters.count >= minimalUsernameLength
    }
    .replay(1)
    // .refCount() ここをコメントアウトすると、subscribeしてるのに何も流れてこない
```


## refCountとは
なぜストリームが流れないのか？その答えのヒントは`ConnectableObservable`という名前です。実は、`ConnectableObservable`は`connect`というオペレータを呼び出すまでは、イベントが発生していてもそれをストリームに流すことはしません。<b>つまり、`connect`しない限りはsubscribeしていてもストリームは流れてこない</b>ということです。

じゃあ`connect`すればええやん、となるのですが一つ面倒なことがあります。それは`connect`した場合、<b>そのソースを適切なタイミングで`dispose`する必要がある</b>という点です。複数のObserverにsubscribeされている場合、この`dispose`を行うタイミングを適切に考えて実装するのはなかかなか手間です。

そこで役立つのが、`refCount`です。`refCount`は、subscribeされると`connect`すると同時に内部のカウンターをインクリメントし、Observerが`dispose`されると内部のカウンターをデクリメントします。そして、カウンターの値が0になると同時に、`connect`したソースを`dispose`します。

iOSのエンジニアならARCでお馴染みのリファレンスカウンタと同じですね。おそらく名前も、referenceCountの略だと思われます。`ConnectableObservable`の末尾に.refCount()をつけておくことで、複数subscribeしても適切に`connect`して`dispose`してくれる、というわけです！

## というわけで
複数のObserverが購読してるストリームで、最初の例の様に2本のストリームを作るのではなく、1本ストリームから同じ値を購読したいんだ、という場合には不必要な処理や意図しない処理が走らないように、`shareReplay`(`share`)をつけておきましょう！

## HotなObservable とColdなObservable / Hot変換
ちなみに、上述した`ConnectableObservable`の特徴は `Hot Observable`と呼ばれる種類の`Observable`の性質の一部です。Rxの世界では`Observable`はHotとColdの2種に区別され、それぞれに異なった性質を持っています。これまでに説明した特徴以外も含めて、下記のRxSwiftのDocumentではわかりやすく対比した表を用意してくれています。

[Rx Swift Hot and Cold Observables](https://github.com/ReactiveX/RxSwift/blob/master/Documentation/HotAndColdObservables.md)

また、`replay`を利用して行ったConnectableObservableへの変換は、Rxの世界では`Hot変換`などと呼ばれます。このあたりの理解については（自分自信まだあまり理解できたとは言えないですが）、下記の様な記事たちが大変参考になりました。
[RxJS の Operators (11) - Connectable Observable Operators](http://qiita.com/bouzuya/items/c93e522b18b752defb77) 
[RxのHotとColdについて](http://qiita.com/toRisouP/items/f6088963037bfda658d3)
[Rx入門 (13) - HotとCold](http://blog.xin9le.net/entry/2012/01/18/105003)
[Rx入門 (14) - Cold to Hot変換](http://blog.xin9le.net/entry/2012/01/20/223111)
[Connectable Observable Operators](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators)