RxSwiftのshareを利用する際の意外と気づきにくい挙動の違い

# はじめに

[AbemaTV Advent Calendar 2017](https://adventar.org/calendars/2216) 5日目の記事です。(5日目過ぎてますが、12/5だけ空いてたので書いてしまいますｗ)
前日の4日目の記事は[@splas_boomerang](https://qiita.com/splas_boomerang)さんの「[Googleが規定する品質要件から見るDaydreamプラットフォームの特徴](https://qiita.com/splas_boomerang/items/287ca1b59e3c89f0964c)」で、翌日の6日目の記事は[@yasuhideshimizu](https://medium.com/@yasuhideshimizu)さんの「[VRのUI作りにおすすめのsketchplugin『Sketch to VR』](https://medium.com/@yasuhideshimizu/vr%E3%81%AEui%E4%BD%9C%E3%82%8A%E3%81%AB%E3%81%8A%E3%81%99%E3%81%99%E3%82%81%E3%81%AEsketchplugin-sketch-to-vr-a99d0ac7e212)」になります。

[RxSwift](https://github.com/ReactiveX/RxSwift)でObservableのHot変換する際に、`.share()`や`.share(replay:scope:)`を利用することがあるかと思います。
本投稿では、`share()`を利用する際に意外と気づきにくい挙動の違いについて書いていこうと思います。

# 利用例

下記のように、`NetworkConnection`が`.mobile`か`.wifi`かによって`QualityHandler`が保持する`qualityList`の内容が変化する実装があるとします。
さらに

- `.mobile`の場合には`selectedQuality`の値は`.low`
- `.wifi`の場合には`selectedQuality`の値は`.middle`

であるとします。

`QualityHandler`は、選択されている`NetworkConnection`と選択される`Index`をinitializerで受け取ります。


```swift
enum NetworkConnection {
    case mobile
    case wifi
}

enum Quality {
    case unknown
    case low
    case middle
    case high
}

final class QualityHandler {
    var qualityList: [Quality] {
        return _qualityList.value
    }
    var selectedQuality: Quality {
        return _selectedQuality.value
    }

    private let _qualityList = Variable<[Quality]>([])
    private let _selectedQuality = Variable<Quality>(.unknown)
    private let disposeBag = DisposeBag()

    init(selectedNetwork: Observable<NetworkConnection>,
         selectedIndex: Observable<Int>) {
        let wifiConnection = selectedNetwork
            .filter { $0 == .wifi }
            .share() // <-- 今回の注目点

        let mobileConnection = selectedNetwork
            .filter { $0 == .mobile }
            .share() // <-- 今回の注目点

        wifiConnection
            .map { _ -> [Quality] in [.middle, .high] }
            .bind(to: _qualityList)
            .disposed(by: disposeBag)
        mobileConnection
            .map { _ -> [Quality] in [.low, .middle] }
            .bind(to: _qualityList)
            .disposed(by: disposeBag)

        selectedIndex
            .withLatestFrom(_qualityList.asObservable()) { ($0, $1) }
            .flatMap { index, list -> Observable<Quality> in
                guard index < list.count else { return .empty() }
                return .just(list[index])
            }
            .bind(to: _selectedQuality)
            .disposed(by: disposeBag)

        // 選択されているのがwifiだった場合、値をmiddleへ
        wifiConnection
            .withLatestFrom(_selectedQuality.asObservable())
            .filter { $0 == .unknown }
            .map { _ -> Quality in .middle }
            .bind(to: _selectedQuality)
            .disposed(by: disposeBag)

        // 選択されているのがmobileだった場合、値をlowへ
        mobileConnection
            .withLatestFrom(_selectedQuality.asObservable())
            .filter { $0 == .unknown }
            .map { _ -> Quality in .low }
            .bind(to: _selectedQuality)
            .disposed(by: disposeBag)
    }
}
```

## selectedNetworkが`PublishSubject`の場合

1つ目のprintでは、また`selectedNetwork`に値が流れていないため、`unknown`となります。qualityListも空になります。
2つ目のprintでは、直前に`.wifi`を流しているので、`middle`となります。qualityListには`[.middle, .high]`が入っています。
3つ目のprintでは、直前に`index1`を流しているので、`high`となります。
それぞれ、期待していた値がprintで表示されています。

```swift
let selectedNetwork = PublishSubject<NetworkConnection>()
let selectedIndex = PublishSubject<Int>()

let handler = QualityHandler(selectedNetwork: selectedNetwork, selectedIndex: selectedIndex)

handler.qualityList.forEach { print($0) } // 何も表示されない
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = unknown

selectedNetwork.onNext(.wifi)
handler.qualityList.forEach { print($0) } // middle high
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = middle

selectedIndex.onNext(1)
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = high
```

## selectedNetworkが`Variable`の場合

`Variable`であるため、QualityHandlerの初期化時に`.wifi`の値が即時に流れます。
1つ目のprintでは、`middle`になっていてほしいところですが、初期値の`unknown`のままになっています。
ところが、qualityListには`[.middle, .high]`が入っています。
2つ目のprintでは、直前でもう一度`.wifi`を入れ直しているので、値が流れて`middle`に変わります。
3つ目のprintでは、直前に`index1`を流しているので、`high`となります。
1つ目が、期待していたものとは違っています。

```swift
let selectedNetwork = Variable<NetworkConnection>(.wifi)
let selectedIndex = PublishSubject<Int>()

let handler = QualityHandler(selectedNetwork: selectedNetwork.asObservable(),
                            selectedIndex: selectedIndex)

handler.qualityList.forEach { print($0) } // middle high
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = unknown

selectedNetwork.value = .wifi
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = middle

selectedIndex.onNext(1)
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = high
```

## selectedNetworkが`Observable.just`の場合

`Observable.just`も`.wifi`の値が即時に流れます。
1つ目のprintでは、`middle`になっていてほしいところですが、初期値の`unknown`のままになっています。
ところが、qualityListには`[.middle, .high]`が入っています。
2つ目のprintでは、直前に`index1`を流しているので、`high`となります。
1つ目が、期待していたものとは違っています。

```swift
let selectedNetwork = Observable<NetworkConnection>.just(.wifi)
let selectedIndex = PublishSubject<Int>()

let handler = QualityHandler(selectedNetwork: selectedNetwork, selectedIndex: selectedIndex)

handler.qualityList.forEach { print($0) } // middle high
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = unknown

selectedIndex.onNext(1)
print("selectedQuality = \(handler.selectedQuality)") // selectedQuality = high
```

# まとめ

上記の違いとしては、`QualityHandler`の初期化時に即時に値が流れるかそうでないかの違いがあります。

`QualityHandler`のinit内の

```swift
let wifiConnection = selectedNetwork
    .filter { $0 == .wifi }
    .share()
```

に一旦フォーカスしてみます。

`wifiConnection`は

```swift
wifiConnection
    .map { _ -> [Quality] in [.middle, .high] }
    .bind(to: _qualityList)
    .disposed(by: disposeBag)

...

wifiConnection
    .withLatestFrom(_selectedQuality.asObservable())
    .filter { $0 == .unknown }
    .map { _ -> Quality in .middle }
    .bind(to: _selectedQuality)
    .disposed(by: disposeBag)
```

の2箇所でsubscribe（bind）されています。

`selectedNetworkがVariableの場合`の1つ目のprintはunknownになっていましたが、`qualityList`には適切な値が代入されていました。
上記の実装からもわかるように、`qualityList`に値が代入されているのは1つ目のsubscribeになります。
`.share()`したObservableの値が上流から即時に流れる場合、2つ目以降ののsubscribeが完了する前に1つ目のsubscribeに対して値が流れてしまっているので、期待していた動きになっていない状態となります。
そのため、`.share()`ではなく`.share(replay:scope:)`を利用すれば、内部で値をキャッシュしているので、期待していた動きになります。
値が即時に流れるかどうかはメソッドの引数がObservableの状態ではわからないので、上流を見て`.share()`を使うのか`.share(replay:scope:)`かを見極めていく必要がありそうです。

# 追記

また、下記のように値を流す用の`PublishSubject`を用意し、全てのsubscribeが完了した後にbindすることで`.share()`を利用したままの実装も可能です。

```swift
final class QualityHandler {
    
    ...

    init(selectedNetwork: Observable<NetworkConnection>,
         selectedIndex: Observable<Int>) {
        let _selectedNetwork = PublishSubject<NetworkConnection>() // 最後に値を流す用

        let wifiConnection = _selectedNetwork
            .filter { $0 == .wifi }
            .share() // <-- 今回の注目点

        let mobileConnection = _selectedNetwork
            .filter { $0 == .mobile }
            .share() // <-- 今回の注目点

        wifiConnection
            .map { _ -> [Quality] in [.middle, .high] }
            .bind(to: _qualityList)
            .disposed(by: disposeBag)

        ...

        // 選択されているのが.mobileだった場合、値をlowへ
        mobileConnection
            .withLatestFrom(_selectedQuality.asObservable())
            .filter { $0 == .unknown }
            .map { _ -> Quality in .low }
            .bind(to: _selectedQuality)
            .disposed(by: disposeBag)

        selectedNetwork // 全てのsubscribeが終わったあとに、bindする
            .bind(to: _selectedNetwork)
            .disposed(by: disposeBag)
    }
}
```