ViewControllerをObservableとして考える

昨日は同僚の @ukitaka 君がスレッド周りの話についてわかりやすく書いてくれました。
一転して僕の話はわかりにくい内容になってしまった。すみません😔

# これはなに
ViewControllerの内側だけでなく、外側もObservableとして考えてみると色々捗るのではないでしょうか、という提案です。
幾つか自分の中で答えが出せていない部分もあるので、その点はご容赦ください。すみません。

# Observableとイベント
RxSwiftを取り入れたプロジェクトでは、様々なイベントをObservableとして表現し、組み合わせ、プログラムを書いていくことになります。
例えば、ユーザがボタンをタップしたり `button.rx.tap`
APIRequestを投げて結果が帰ってきたり `urlSession.rx.send(apiRequest)`
そういった様々なイベントを組み合わせてプログラムを書きます。
「ユーザがボタンをタップしたらAPIを叩いてその結果をTextViewに表示する」というのは

```swift
button.rx.tap
  .flatMapFirst { urlSession.rx.send(apiRequest) }
  .map(String.init)
  .observeOn(Mainscheduler.instance)
  .bindTo(textView.rx.text)
  .addDisposableTo(disposeBag)
```

みたいな書き方になると思います。
そして、Observableを使うことの興味関心の多くは、ViewControllerの内側に向いている気がするな、と個人的に思っていたりします。
(もちろん、色々な宗教があってロジックはどこに書くんだみたいな話題があることは存じていますが、それも含めて。)


# ViewControllerとタスク
ViewControllerはしばしばユーザの目的、タスクを表現するコンポーネントとして利用されています。
例えば`UIImagePickerController`は、写真を選択するというタスクであり、
例えば`UIAlertController`は、複数の選択肢から一つを選択するというタスクです。
多くの人はログイン画面を作ったことがあると思います。もちろんこれもログインする、というタスクですね。
これらのタスクは、ViewControllerの外側、例えば呼び出し元のViewControllerやアプリのステートに作用します。なので、多くの場合はDelegateやHandlerを使うのではないでしょうか。

ViewControllerとタスクの関係は、次のような性質があると考えています。
1. タスクが終了するとき、ViewControllerは画面から表示されなくなる。
2. タスクは結果を伴うことがある。

あれ、これってもしかして…


# Observableとタスク
タスクをObservableに当てはめて考えてみると、
1. タスクの終了は`Completed`
2. タスクの結果は`Next`

として表現できます。
例えば`UIImagePickerController`でキャンセルボタンを押した場合は、`Completed`だけが流れてくる、
写真を選択した場合は、`Next(Picture)`と`Completed`が流れてくるイメージです。
実際の実装としては、キャンセルや写真の選択のイベントは、`UIImagePickerControllerDelegate`に宣言されているので、RxDelegateを実装して、Observableを合成すればうまくいきます。
`UIImagePickerController`のRx実装は、[本家のRxExample](https://github.com/ReactiveX/RxSwift/blob/master/RxExample/Extensions/UIImagePickerController%2BRx.swift)を拝借しましょう。

```swift
extension Reactive where Base: UIImagePickerController {
  public var task: Observable<[String: AnyObject]> {
    return Observable
      .of( // キャンセルと写真の選択をOptionalで表現して型を揃えます
        didFinishPickingMediaWithInfo.map { Optional.some($0) },
        didCancel.map { Optional.none }
      )
      .merge()
      .take(1) // 最初の1回目を採用します
      .flatMap { Observable.from($0) } // .someだけ.nextとして流します
  }
}
```

`UIImagePickerController`のタスクを、Observableとして表現することができました。
タスクは色々なイベントを組み合わせて実現しているので、Observableで表現できるのは至極当たり前ですが、実際に書いてみると色々腑に落ちてきます。

さて、`rx.task`を使うと、「ユーザがボタンをタップしたらImagePickerを表示して選択された画像をImageViewに表示する」というのが

```swift
button.rx.tap
  .flatMapFirst { [unowned self] in
    let imagePicker = UIImagePickerController()
    return imagePicker.rx.task
      .do(
        onCompleted: { imagePicker.dismiss(animated: true, completion: nil) }
        onSubscribe: { self.present(imagePicker, animated: true, completion: nil) }
      )
  }
  .map { $0[UIImagePickerControllerOriginalImage] as? UIImage }
  .bindTo(imageView.rx.image)
  .addDisposableTo(disposeBag)
```

このように記述できるようになりました。
Taskを持つViewControllerをprotocolとして表現して、`flatMapFirst`の中身をUIViewControllerに`rx.present`の実装として移動させると、

```swift
button.rx.tap
  .flatMapFirst { [unowned self] in
    self.rx.present(UIImagePickerController())
  }
  .map { $0[UIImagePickerControllerOriginalImage] as? UIImage }
  .bindTo(imageView.rx.image)
  .addDisposableTo(disposeBag)
```

このように記述することができるようになります。
`rx.present`が画面遷移について担保しているので、`UIImagePickerController`は、タスクについてのみ考えておけば良いですね。

# 問題点とか
Swift3.0では`extension Reactive where Base: XXX`に特定の関数を持つ型、というのは表現できませんので、`rx.present`の実現のためには、何かしらのWorkaroundは必要です。
僕が今携わっているProjectでは、

```swift
protocol ObservableViewController {
  associatedtype Result
  func result() -> Observable<Result>
}
```
という型を作って、これを実装したViewControllerを引数に取る`rx.present`を書いていますが、以下の2点の問題があってどうしようかな~と考えているところです。
1. resultがObservableなのにReactive以下に置くことができない。
2. クラスに対してResultが一意に決まってしまう。`UIAlertController`をPrompt、またはConfirmとしてのみ使うことができなくなってしまう。かと言ってサブクラスを作るのは違う気がする…
Task型をViewControllerより外側に置くのは良いかもしれない、どうするかな~

もう一つは、Completedを流すということは、ViewControllerは再利用するべきではないので、再利用し得るシチュエーションについて、考察を深める余地があります。
例えば`UIImagePickerController`から更に次の画面をPushする場合など。次の画面でキャンセルすると、`UIImagePickerController`に戻ってきますが、`rx.task`はsubscribeしなおさないと`Completed`が流れてしまったあとです。
再利用するということは、正確にはタスクは完了していないので、`Completed`を流すべきではない、のかもしれない。うーん🤔

# まとめ
ViewControllerに含まれるタスクと、画面遷移のロジックを分離して、タスクをObservableで表現することを考えてみました。
画面遷移を伴うプログラムを、Observableに記述することができるようになります。やったー
タスクを持つViewControllerを表現するために、どういう型を使うべきか、というのはPlaygroundと戯れてますが、まだ見いだせていません。