ライブラリを使わずにMV*の話(iOS)〜Modelに状態を持たせて状態遷移を行う〜

# この記事は

MV*というかModelの話がメインです

## 結論
- Modelに **状態** を持たせ、 **状態遷移** を行うようにする。(= Modelをステートマシンとして使う)

## 話すこと
- ステートマシンとは
    - 状態とは
    - 状態遷移とは

## 前提

- Model
    - ユーザーに提示する状態・値を持っている
    - 内部状態の変更を通知(Observerパターン)で知らせる

### 参考?

この記事のみでも成り立つように作成していますが、上記の前提を詳しく話しているのは別の記事になります。

- [ライブラリを使わずにMV*の話(iOS)~ViewとModelの役割〜](https://qiita.com/yokoyas000/items/5aeff895233f9c1af9d4)

# ステートマシンの話に入る前に

なぜステートマシンが必要になるのか。
アプリ開発のよくある要件を例に話を始めます。

## よくある要件

- 画面上にボタンがある
- ボタンを押したらサーバーと通信して情報を取得する
- 通信中 -> インジケータを表示したい
- 通信中は、再度ボタンをタップされても何もしないようにしたい
- 通信失敗 -> アラートを表示したい
- 通信成功 -> 結果を画面へ反映したい


## それぞれをフラグ管理した場合

要件を実現するために、Modelにフラグを用意してみます。

```swift
class ProcessModel {

    // 通信中フラグ
    private(set) var isDoing: Bool

    // 成功時は結果のStringが入る
    private(set) var result: String?

    // 失敗時はErrorが入る
    private(set) var error: Error?

    func execute() {
        // 通信中の場合は何もしない
        guard !self.isDoing else {
            return
        }
        self.isDoing = true

        /**
         なんらかの非同期な通信処理
         コールバックで通信結果を受け取るとする
         */
        API.someProcess { processResult in
            // 通信成功の場合
            if processResult.isSuccess {
                self.result = self.createResult(from: processResult)

            // 通信失敗の場合
            } else {
                self.error = self.createError(from: processResult)
            }
        }
    }

}
```


## Modelの取りうる状態

フラグが３つありますが、この場合Modelの取りうる状態は全部で何パターンあるでしょうか？

| No. | isDoing | result | error |
|---|----|----|----|
| 1 | true | nil | nil |
| 2 | true | nil | Error |
| 3 | true | String | nil |
| 4 | true | String | Error |
| 5 | false | nil | nil |
| 6 | false | nil | Error |
| 7 | false | String | nil |
| 8 | false | String | Error |


全8パターン。
実はこの時点で既に問題が発生しています。
個別のフラグ管理 = **「このModelはどういう状態をとるのか」の全パターンが分かりづらい** という問題です。

そして良く見ると、どういう状態なのかよく分からないパターンが...。
No.2: 通信中かつエラーがある場合?
No.3: 通信中かつ結果がある場合?
No.4,8: 結果もエラーもある場合?

既によくわからないパターンが含まれていますが、ここからさらに仕様を追加されたらどうなるでしょうか？

## さらにフラグが増えた場合

仕様追加「処理Aが実行中は処理Bを行わないようにしたい」
-> isProcessADoing, isProcessBDoing の発生

フラグが増えたので、パターン数も増えました。
ついでに、あり得なさそうな(あり得てはいけないはずの)状態も増えました。
(isProcessADoing = true かつ isProcessBDoing = true とか)

つまり、 **フラグが増える = 無駄なパターンも増える**

きつい💀

## 問題点と要望

- 問題点
    - 現状では「このModelはどういう状態をとるのか」の全パターンが分かりづらい
    - フラグが増える = 無駄なパターンも増える

- 要望
    - 取りうる状態の一覧が欲しい
    - 無駄なパターンは省きたい

## Enumが便利ですよ

要望を叶えるためにはどう変更したらいいか考えてみます。

「取りうる状態の一覧が欲しい」 
-> Enumが良さそう。

ということで、状態を表すのにEnumが便利なので使います。
しかし今度は、どういうEnumを定義すればいいのかという疑問が出てきます。

そこで、状態(Enum)を持ってるModelのことを、ステートマシンとして使うと良いよという話をします。

# ステートマシンとは

- 状態を持ち、 状態遷移を行うクラス
- 簡単に言うと、状態遷移図を実装したクラス

## 状態とは

通信中, 通信失敗, 通信成功 などを指しています。

## 状態遷移とは

文字通り状態が移り変わることです。

要件を満たす状態遷移図はこんな感じ
<img width="568" alt="スクリーンショット 2017-10-06 14.26.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/97259/fffa1706-264b-39e8-cb1f-7611f8530766.png">


**Modelをステートマシンとして使う = 上記の状態遷移図を満たすようにModelを実装する**
ということになります。

# 具体的なコードを書いてみる

## 状態の一覧をEnumで定義する

状態遷移図を元に、取りうる状態をEnumで定義します。

```swift:State
enum State {
    // 何もしていない
    case sleeping

    // 通信中
    case doing

    // 通信成功
    case success(result: String)

    // 通信失敗
    case failure(error: Error)
}
```

状態の定義はこれでOK...?🤔

## 状態遷移図を見直してみる

通信中のあと 
-> 通信成功 or 通信失敗
-> **通信成功 と 通信失敗は同列で扱うべきっぽい**

下記のように変更

```swift:State
enum State {
    case sleeping
    case doing
    case done(Result)

    enum Result {
        case success(result: String)
        case failure(error: Error)
    }
}
```

状態の定義はこれでOK🎉

## Modelに状態、状態遷移を持たせる

定義した状態を使うようにModelを書き換えます。

```swift:ProcessModel
class ProcessModel {
    // 状態をModelに持たせる
    // 状態: 何もしていない
    private(set) var currentState: State = .sleeping

    func execute() {
        // 状態: 通信中
        self.currentState = .doing

        /**
         なんらかの非同期な通信処理
         コールバックで通信結果を受け取るとする
         */
        API.someProcess { processResult in
            // 通信結果: 成功
            if processResult.isSuccess {
                let result = self.createResult(from: processResult)

                // 状態: 通信成功
                self.currentState = .done(.success(result: result))

            // 通信結果: 失敗
            } else {
                let error = self.createError(from: processResult)

                // 状態: 通信失敗
                self.currentState = .done(.failure(error: error))
            }
        }
    }

}
```

これで状態遷移図を満たせているでしょうか。

## 連打対策

ぱっと見は大丈夫そう。
ですが、実は状態遷移図にない **余計な遷移** をしてしまっている箇所があります。

現状を表す状態遷移図
<img width="718" alt="スクリーンショット 2017-10-06 14.25.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/97259/aab0d3ba-9bb0-5793-e24b-c932ada120ef.png">

`通信中 -> 通信中` の矢印です。
この矢印があると、 要件の「通信中は、再度ボタンをタップされても何もしないようにしたい」を満たさないので、Modelを修正します。

```swift
class ProcessModel {
    // ... 中略 ...

    func execute() {
        switch self.currentState {
        case .doing:
            // 何もしない
            return
        case .sleeping, .done:
            // 状態: 通信中
            self.currentState = .doing
        }

        // ... 以下同じ...
    }
}
```

# 要望は満たせたか？

- 取りうる状態の一覧が欲しい
    - ⭕️　-> Enumが一覧になる
- 無駄なパターンは省きたい
    - ⭕️　-> 状態遷移図を書いた時点で省かれている

yeei😎

# 処理が複数ある場合は状態が複数必要なのでは？

仕様追加「処理Aが実行中は処理Bを行わないようにしたい」に対応するためには？


## 処理ごとにModelをわけ、２つのModelを取り持つModelを作る

処理A, 処理BごとにModel作り、Model同士を取り持つクラスをつくります。
「取り持つクラス」も立ち位置はModelなのですが、ただのModelと区別するためにModelMediator(Modelの仲介者)と名付けています。
(名前はModelでもいいです（宗教感ある))

```swift:ModelMediator
class ModelMediator {

    private let processAModel: ProcessAModel
    private let processBModel: ProcessBModel

    // 要件: 処理Aが実行中は処理Bを行わないようにしたい
    func executeProcessB() {
        switch self.processAModel.currentState {
        case .doing:
            // 処理Aが実行中なので何もしない
            return

        case .sleeping, .done:
            self.processBModel.execute()
        }
    }
}
```

Modelへ指示する側では、直接Modelを持たずにModelMediatorを持ちます

```swift:Controller
class Controller {
    // ModelMediatorを持つ
    private let modelMediator: ModelMediator

    init(modelMediator: ModelMediator) {
        self.modelMediator = modelMediator
    }

    func executeProcessB() {
        // 各Modelが今どういう状態か、はControllerで考慮しない -> ModelMediatorにやらせる
        self.modelMediator.executeProcessB()
    }
}
```
## Modelを分ける意味はあるのか

Q. Model同士を取り持つクラスがいるなら、初めから1つのModelにしてしまった方が楽なのでは？
A. 処理Aと処理Bは別のものなので、別Modelにできるはず。別Modelのままにすることで、それぞれのModelの単純さを保ちたい。

処理Aは実行中に処理Bのことは考えません。逆も同じです。
処理Bは開始時こそ処理Aのことを考えますが、実行を開始した以降は処理Aのことを考えません。
処理Aと処理Bが関係するのは、「処理Bの開始時に処理Aが実行中だった場合」だけです。

ある一点でしか関係しないのでしたら、「その一点を表すクラス(Model)」を外部に用意する方が、各Modelは単純で済みます。

# まとめ

- Modelをステートマシンとして使うと便利
    - 状態の一覧があるので、 Modelの取りうる状態がわかりやすい
    - 無駄なパターンを省ける
- Modelが複数必要になった場合は、取り持つクラスをModelの外部につくる。そうすることで、それぞれのModelは単純さを保てる


# 参考
- [知らないと損するアプリ開発におけるStateMachineの活用法（full版）](https://www.slideshare.net/mokemokechicken/statemachinefull)
- [iOS でテスト容易な設計を 実現するためのデザインパターン](https://speakerdeck.com/orgachem/ios-detesutorong-yi-nashe-ji-wo-shi-xian-surutamefalsedezainpatan?slide=115)