Swift 3でSwiftBondを使ってMVVMしちゃおう

# 概要
## 対象
本稿は、2009年11月18日にMicrosoft DevLabsにて実験的に公開され、その後 `.NET Framework` や `Silverlight` に導入された、ReactiveExtensionの流れを汲む`RxSwift`や`ReactiveCocoa`を使うと綺麗に設計ができそうだということを小耳に挟みながらも、なかなか触れる機会がない、あるいは導入まで至っていないiOS開発者の方々が、Reactiveなプログラミングによって得られるメリットを、動くサンプルを通じて実感することを目的に書かれています。

## 内容など
- 今回は、APIが比較的コンパクトにまとまっていて、学習コストの低い [SwiftBond](https://github.com/ReactiveKit/Bond) を使って、オブザーバパターンを使ったMVVM設計への導入部分を案内します。SwiftBondの内部の実装等については次回以降記載します。
- 新しく知るべきこと、理解するべきことを可能な限り減らした解説記事にしています。
- 今回、Reactiveの詳しい話やオブザーバーパターンの解説は行いません。
- 次回以降は、データベース接続や通信を行うアプリケーションでのMVVMパターンの実装例について書く予定です。

## 環境
- Xcode 8.2
- Bond 5.3
- Swift 3

# 動機
オブザーバパターンを用いたMVVMパターンでiOSアプリケーションを設計することで、以下のようなiOSのアプリケーションの厄介な特性に抗って、見通し良く、テスト可能な形に設計を行うことができます。

- 様々な種類の状態を持ち管理が煩雑。
- ViewとModelの両方に対して双方向のやり取りが必要になるため、それらの管理をViewControllerで行ってしまい、VCが巨大になりがち。
- ViewControllerをテストし辛いことから、アプリケーションの振る舞いのうちで、テストされていない部分が多い。


# はじめの一歩
## 準備
```
git clone git@github.com:kentrino/swift-bond.git
cd swift-bond
pod install
```

## プロジェクトにBondを追加
上のリポジトリではすでに追加されています。

```diff
+ pod 'Bond', '~> 5.3'
```

をPodfileに追加したあと`pod install`を行う。

## 一番単純なサンプル
`input`(`UITextField`)に入力された値をそのまま、`label`（`UILabel`）に反映する（単方向のバインディング）

```swift
import UIKit
import Bond

class StartViewController: UIViewController {

    @IBOutlet weak var input: UITextField!
    @IBOutlet weak var label: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        input.bnd_text
            .bind(to: label.bnd_text)
    }
}
```

先程のリポジトリで `git checkout minimum` することでこのサンプルコードに切り替えることができます。

## ちょっと工夫したサンプル
`input` に入力された値を少し加工して、`label` に反映する。

```diff

        input.bnd_text
+            .map { (str: String?) -> String? in
+                return  "Your input is '\(str!)'!"
+            }
            .bind(to: label.bnd_text)
```

これだけの挙動であれば、あまり面白みはあまりありませんが、アプリケーションの随所でこのように書きたいところは多々あると思います。

先程のリポジトリで `git checkout second-minimum` することでこのサンプルコードに切り替えることができます。

# 次の一歩の前に
## Observableについて
SwiftBondを使うにあたって避けて通れないのが `Observable` の理解です。実は、エラー状態を扱ったりもできますが、今回はその基本的な挙動の解説のみを行います。

### Observableとは
値の変更を監視して、変更に応じた挙動を記述することができる便利な変数です。Swiftのクラスのプロパティに`didSet`を宣言することで、値の変更に従った挙動を定義することができますが、それと似ています。`Go`の `Channel` と大体同じと考えることもできます。

### 宣言
```swift
let intObservable = Observable<Int>(0)
```

intの値を出し入れ可能な `Observable` を宣言しています。

### 変更に応じた挙動の定義
変数 `intObservable` に格納されている値が変更されたときにobserveNextの引数に指定されたクロージャーが呼ばれます。

```swift
        let _ = intObservable.observeNext{ (i: Int) -> Void in
            if i == 3 {
                print("san!")
            } else {
                print(i)
            }
        }
```


 `intObservable` に現在格納されている値は`intObservable.value`で取り出すことができ、以下のコードは上のコードと同じ結果を出力します。


```diff
        let _ = intObservable.observeNext{ (i: Int) -> Void in
            if i == 3 {
                print("san!")
            } else {
-                print(i)
+                print(intObservable.value)
            }
        }
```


変数 `intObservable` に新たに値の格納が行われないと通知されたときに、行う動作を記述します。


```swift
        let _ =  observable.observeCompleted { _ -> Void in
            print("Last value emitted.")
        }
```

### 変更の通知
`intObservable` に格納されている値を変更します。

```swift
        intObservable.next(1)
        intObservable.next(2)
        intObservable.next(3)
```

これ以上変更が起こらないことを通知します

```swift
        intObservable.completed()
```

先程のリポジトリで `git checkout observable` することでこのサンプルコードに切り替えることができます。

### Observableを使った先程のバインディングの実装

こんな感じになります。

```swift
class StartViewController: UIViewController {

    @IBOutlet private weak var input: UITextField!
    @IBOutlet private weak var label: UILabel!
    
    private let observableText = Observable<String>("")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        input.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
        
        observableText.observeNext{ (text: String) -> Void in
            self.label.text = text
        }.disposeIn(bnd_bag)
    }
    
    func textFieldDidChange(textField: UITextField) {
        observableText.next(textField.text!)
    }
}
```

`.disposeIn(bnd_bag)` というメソッド呼び出しがありますが、こちらの解説は次回以降に回します。
先程のリポジトリで `git checkout use-observable` することでこのサンプルコードに切り替えることができます。


# 次の一歩
道具が揃ったので、MVVMパターンを使ったストップウォッチの実装に移ります。スタート機能、レジューム機能、ストップ機能、クリア機能が付いたストップウォッチを実装します。

## まずはモデルから
こんな感じです。普通のストップウォッチですが、ストップウォッチが示す時間を表す `time` と、ストップウォッチの状態を表す `state` が `Observable` になっています。ストップウォッチの状態をenumで表現しています。 途中、 `DisposeBag` という見慣れぬクラスが登場していますが、この解説は次回に回します。

```swift
import Foundation
import Bond
import ReactiveKit

class StopWatch {
    
    enum State {
        case startable
        case stoppable
        case resumable
    }
    
    var time = Observable<TimeInterval>(TimeInterval(0))
    var state = Observable<State>(.startable)
    
    let disposeBag = DisposeBag()
    
    private var timer: Timer?
    private var startTime: Date?
    private var initialTime: TimeInterval = 0
    
    func start() {
        startTimer(withInitialTime: 0)
        state.next(.stoppable)
    }
    
    func resume() {
        startTimer(withInitialTime: initialTime)
        state.next(.stoppable)
    }
    
    func stop() {
        timer?.invalidate()
        initialTime += Date().timeIntervalSince(self.startTime!)
        state.next(.resumable)
    }
    
    func clear() {
        initialTime = 0
        state.next(.startable)
    }
    
    private func startTimer(withInitialTime initialTime: Double) {
        startTime = Date()
        timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer: Timer) -> Void in
            let time = Date().timeIntervalSince(self.startTime!) + initialTime
            self.time.next(time)
        }
    }
}
```
## Viewに思いを巡らせる
今回は、時間の表示画面とメインのボタン（ストップウォッチの状態に応じて、レジューム、ストップ、スタートが切り替わる）、とクリアボタンだけがあるものを作ります。これを念頭において、次の`ViewModel`の実装へ移ります。

## 次にViewModel
`StopWatchViewModel`を実装して行きます。モデルの状態にどのビューの状態が対応するかを宣言的に記述するだけなので、このクラスに状態を導入する必要はありません。よく言われている「ViewModelはModelの影」を徹底しつつ実装します。

まず ViewModel の class を作成します。

```swift
import Bond

class StopWatchViewModel {

}
```

### 内部クラス: Action
やや過剰設計ですが、簡単のため、内部クラス `Action` を導入します。当然、不変なプロパティのみをもたせます。

```swift
  class Action {
      // Actionの文字列表現
      let description: String
      // Actionが実行可能かどうか
      let enabled: Bool
      // Actionが実行する動作
      let `do`: () -> Void
      
      init(description: String, enabled: Bool, `do`: @escaping () -> Void = {}) {
          self.description = description
          self.enabled = enabled
          self.do = `do`
      }
  }
```

### StopWatchの状態に対応する、メインボタン用のActionを作る処理
```swift
private let stopWatch = StopWatch()

private func getMainAction(fromState state: StopWatch.State) -> Action {
    let start: () -> Void = {
        self.stopWatch.start()
    }
    
    let stop: () -> Void = {
        self.stopWatch.stop()
    }
    
    let resume: () -> Void = {
        self.stopWatch.resume()
    }
    
    switch state {
    case .startable:
        return Action(description: "Start", enabled: true, do: start)
    case .stoppable:
        return Action(description: "Stop", enabled: true, do: stop)
    case .resumable:
        return Action(description: "Resume", enabled: true, do: resume)
    }
}
```

### StopWatchの状態に対応する、クリアボタン用のActionを作る処理
```swift
private func getClearAction(fromState state: StopWatch.State) -> Action {
    let clear: () -> Void = {
        self.stopWatch.clear()
    }
    
    let disabledAction = Action(description: "Clear", enabled: false, do: {})
    
    switch state {
    case .startable:
        return disabledAction
    case .stoppable:
        return disabledAction
    case .resumable:
        return Action(description: "Clear", enabled: true, do: clear)
    }
}
```

### StopWatchが示す時間を文字列に加工する処理
```swift
    private static func string(fromInterval interval: TimeInterval) -> String {
        let centiSecond = Int(interval * 100) % 100
        let second = Int(interval) % 60
        let minutes = Int(interval / 60) % 60
        let hour = Int(interval / 3600) % 100
        return String(format: "%02i:%02i:%02i.%02i", hour, minutes, second, centiSecond)
    }
```

### ViewとつなぐためのObservableのプロパティを用意
```swift
    let time = Observable<String>(StopWatchViewModel.string(fromInterval: TimeInterval(0)))
    let clearAction = Observable<Action>(Action(description: "Clear", enabled: false))
    let mainAction = Observable<Action>(Action(description: "Start", enabled: true))  
```

### バインディング
モデルの値をプロパティにバインディングします。モデルにアクセスするのはこの部分だけです。

```swift
init() {
    stopWatch.time
        .map{ (time: TimeInterval) -> String in
            return StopWatchViewModel.string(fromInterval: time) }
        .bind(to: time)
    
    // stopWatchの状態に応じてmainActionの変更を通知
    stopWatch.state
        .map{ (state: StopWatch.State) -> Action in
            return self.getMainAction(fromState: state) }
        .bind(to: mainAction)
    
    // stopWatchの状態に応じてclearActionの変更を通知
    stopWatch.state
        .map{ (state: StopWatch.State) -> Action in
            return self.getClearAction(fromState: state) }
        .bind(to: clearAction)
}
```

## 最後にView
「ViewはViewModelの影」なので、虚心坦懐にバインドするだけです。逆に言えば、素直にバインドできないということは、ViewModelがうまく作れていないということの証左だと言えます。`someButton.bnd_tap.observe {}`によってボタンを押したときの挙動を指定できて便利なのでこれを使います。

```swift
import UIKit
import Bond

class StartViewController: UIViewController {
    
    @IBOutlet weak private var label: UILabel!
    @IBOutlet weak private var mainButton: UIButton!
    @IBOutlet weak private var clearButton: UIButton!
    
    private let stopWatchViewModel = StopWatchViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        mainButton.bnd_tap.observe { _ in
            self.stopWatchViewModel.mainAction.value.do()
        }.disposeIn(bnd_bag)
        
        clearButton.bnd_tap.observe { _ in
            self.stopWatchViewModel.clearAction.value.do()
        }.disposeIn(bnd_bag)
        
        stopWatchViewModel.mainAction.observeNext { (action: StopWatchViewModel.Action) -> Void in
            self.mainButton.setTitle(action.description, for: .normal)
        }.disposeIn(bnd_bag)
        
        stopWatchViewModel.clearAction.observeNext { (action: StopWatchViewModel.Action) -> Void in
            self.clearButton.setTitle(action.description, for: .normal)
            self.clearButton.isEnabled = action.enabled
        }.disposeIn(bnd_bag)
        
        stopWatchViewModel.time.bind(to: label)
    }
}
```

上述のリポジトリで `git checkout stopwatch` することで最後のサンプルコードに切り替えることができます。

# サンプルを振り返り
サンプルを振り返って、改めて見直してみると、以下の特徴を備えていることが見て取れると思います。

1. 全ロジックがViewから切り離されている
2. ViewModel、Viewが状態をプロパティに持たず、ViewはViewModelにある情報をただ表示しているだけですし、ViewModelにはModelの状態と、ViewModelの状態の「対応関係」だけが書かれている


# まとめ
## ViewModelとViewの責務
### ViewModelの責務

1. Modelの状態がどのようにViewModelの状態に反映されるかを宣言的に記述する。
2. Modelの戻り値のないメソッドを呼ぶ。


### Viewの責務

1. ViewModelの内容を忠実に画面に反映する。
2. ユーザーのアクションと、ViewModelの戻り値のないメソッド呼び出しを対応させる。


## ViewModelがあることで(MVVMで設計することで)何が期待できるか

1. ViewController(View)に状態を記述する必要がなくなる。
2. ViewControllerがすっきりする。
3. テストされていないロジックが減る（次回以降実際に書いてみます）

# 参考文献
http://www.atmarkit.co.jp/fdotnet/introrx/introrx_01/introrx_01_01.html
http://ugaya40.hateblo.jp/entry/model-mistake