【iOS】RxSwiftでのタイマーとflatMapLatestについて

## やりたいこと
RxSwiftを使ってタイマーを実現したい。
プロダクトでRxSwiftを導入しているので、`Timer`クラスを使わずに、RxSwiftの`interval(_ period: RxTimeInterval, scheduler: SchedulerType)`で時間を計測しようと思った。


## とりあえず実装
`UISwitch`のON・OFFでタイマーのStopとStartを管理する簡単なSample

```swift
class ViewController: UIViewController {
    
    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var timerSwitch: UISwitch!
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        timerSwitch.rx.value
            .flatMap { $0 ? Observable<Int>.interval(0.1, scheduler: MainScheduler.instance) : .empty() }
            .map { String($0) }
            .bind(to: timerLabel.rx.text)
            .disposed(by: disposeBag)
    }
}
```

スクリーンショット
![timer1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209811/ad3bb68c-2726-9325-4559-a375886debeb.gif)


`結果`
うまく動いていない。。。
switchをOFFにしてもtimerが止まらない。かつ、2度目のONで2つのTimerが動いているような挙動

## とりあえずデバッグ
各イベントの後に`debug()`を仕込む

```swift
timerSwitch.rx.value
    .debug("switch")
    .flatMap { $0 ? Observable<Int>.interval(0.1, scheduler: MainScheduler.instance) : .empty() }
    .debug("timer")
    .map { String($0) }
    .bind(to: timerLabel.rx.text)
    .disposed(by: disposeBag)
```

デバッグ結果

```
2019-04-12 11:59:02.794: switch -> subscribed
2019-04-12 11:59:02.795: switch -> Event next(false)
2019-04-12 11:59:04.193: switch -> Event next(true) <- Switch On
2019-04-12 11:59:04.293: timer -> Event next(0)
2019-04-12 11:59:04.394: timer -> Event next(1)
2019-04-12 11:59:04.494: timer -> Event next(2)
2019-04-12 11:59:04.593: timer -> Event next(3)
2019-04-12 11:59:04.642: switch -> Event next(false) <- Switch Off
2019-04-12 11:59:04.693: timer -> Event next(4)      <- あれ？ 動いている
2019-04-12 11:59:04.794: timer -> Event next(5)
2019-04-12 11:59:04.893: timer -> Event next(6)
2019-04-12 11:59:04.994: timer -> Event next(7)
2019-04-12 11:59:05.248: switch -> Event next(true)　<- Switch On
2019-04-12 11:59:05.293: timer -> Event next(8)
2019-04-12 11:59:05.349: timer -> Event next(0)      <- 2つ目のTimerが起動
2019-04-12 11:59:05.394: timer -> Event next(9)
2019-04-12 11:59:05.449: timer -> Event next(1)
2019-04-12 11:59:05.493: timer -> Event next(10)
2019-04-12 11:59:05.550: timer -> Event next(2)
```

`flatMap`内で生成した`Observable.interval`が破棄されずにイベントを発火し続けている。

## `flatMap`と`flatMapLatest`について
### `flatMap`
```swift
// Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence.
public func flatMap<O>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E> where O : ObservableConvertibleType
```

<img width="640" alt="flatMap.c.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209811/c608ffd6-95c5-97eb-1fb9-7257e0cb7ff2.png">
新しいObservableを生成して、生成したObservableを1つのObservableにマージする。

上のマーブル図では赤のイベントでObservableが生成されて、次の緑のイベントでもObservableが生成されて、赤と緑のObservableがマージされたObsrvableのイベントが結果流れている。

### `flatMapLatest`

```swift
// It is a combination of `map` + `switchLatest` operator
public func flatMapLatest<O>(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable<O.E> where O : ObservableConvertibleType
```
→ 簡単に言えば `map` + `switchLatest`

なら`switchLatest`は？

<img width="640" alt="switch.c.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209811/b8145a15-534d-ac42-c228-1655360b9962.png">
新しい内部のObservableを受信するたびに、前の内部のObservableの購読を中止して、最新のObservableを購読する。


### `flatMap` と `flatMapLatest` の違いは？
→ `merge`か`switchLatest`の違い。

`flatMap`は新しく生成されたObservableが`merge`されて、`flatMapLatest`では`switchLatest`される。
なので、`flatMap`の場合はイベントで生成されたObservableが`merge`されるので、生成されたObservableがcompletionしない限り増えていくが、`flatMapLatest`は新しくObservableが生成されると前のObservableの購読を中止するため増えない。

今回のTimerが増えていったのは、`flatMap`を使っていたためTimerのObservableが次々にmergeされたからである。



## 改善
`flatMap`の箇所を`flatMapLatest`に変えて無事に動きました。

```swift
class ViewController: UIViewController {
    
    @IBOutlet weak var timerLabel: UILabel!
    @IBOutlet weak var timerSwitch: UISwitch!
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        timerSwitch.rx.value
            .flatMapLatest { $0 ? Observable<Int>.interval(0.1, scheduler: MainScheduler.instance) : .empty() }
            .map { String($0) }
            .bind(to: timerLabel.rx.text)
            .disposed(by: disposeBag)
    }
}
```

## まとめ
- インクリメンタルサーチなどでUIの更新を抑えるために使われる`flatMapLatest`の他の使い所
- Observable.interval()`は、`Timer`と比べて、インスタンスの管理が楽で使いやすい
- `flatMap`, `flatMapLatest`, `switchLatest`, `merge`の理解

## 参考
- [github - RxSwift](https://github.com/ReactiveX/RxSwift)
- [reactiveX - flatmap](http://reactivex.io/documentation/operators/flatmap.html)
- [reactiveX - switch](http://reactivex.io/documentation/operators/switch.html)