VIPERアーキテクチャでプロダクトのiOSアプリを1から作ったまとめ

仕事でiOSアプリをVIPERアーキテクチャで1から書く機会があり、せっかくなのでその知見をまとめることにしました。
なぜ採用したか？実際作ってみてどうだったか？まで、赤裸々にまとめたのでアークテクチャ選定の参考になれば幸いです。

VIPERに関してはPotatotipsでも発表した資料もあるのでこちらもご覧ください。
(サンプルアプリは、TwitterクライアントにしたけどGitHubクライアントに書き直すかも) 

* [スライド](https://speakerdeck.com/hirothings/iosnaakitekutiyav-iperfalsesusume)
* サンプルアプリ
 * https://github.com/hirothings/ViperTwitterClient

## VIPERとは？
各レイヤーの頭文字の組み合わせで **VIPER** と呼びます。

* **V**iew
* **I**nteractor
* **P**resenter
* **E**ntity
* **R**outer

![viper_architecture-2000x720.jpg](https://qiita-image-store.s3.amazonaws.com/0/30423/6be76275-c5c2-e91d-2b74-3d1cdb3f8fd7.jpeg)

最大の特徴は、クリーンアーキテクチャをベースにした**iOSアプリのために作られたアーキテクチャ**であるということです。Routingの層がデフォルトで存在しています。

### 各レイヤーの役割と説明

<img width="1440" alt="Viper-Module.png" src="https://qiita-image-store.s3.amazonaws.com/0/30423/04e8c871-fc60-dd74-41a4-fcb6a4f28cda.png">
##### View
ViewController, Viewなど

##### Interactor
* Presenterのイベントに応じてユースケースごとにデータの取得、加工をする層
* バリューオブジェクト(Entity)を操作するビジネスロジックが含まれる
* Viewからは完全に分離している

##### Presenter
* Viewに対するプレゼンテーションロジックを持つ層
* ViewとRouter, Interactorの橋渡し的存在
* Viewから受け取ったイベントをもとにInteractorにデータを要求する
* Viewから受け取ったイベントをもとにRouterに画面遷移を依頼する
* Interactorから受け取ったデータをViewに渡す

##### Entity
* バリューオブジェクト

##### Router
* 画面遷移を管理する
* 各レイヤーのインスタンスを生成し、画面を描画する(依存性をひとまとめに解決する層はここ)

単一責任の原則をもとに作られたアーキテクチャであり、各レイヤーはInterface(Protocol)にのみ依存している。

## なぜVIPERを選んだか？
今までMVC, MVVMでアプリを書いた経験の中で、下記の課題を持っていました。

* ルーティングはセグエやViewControllerに依存せず分離したい
* データの取得・加工だけをする層がないと結果、Viewに関するプレゼンテーションロジックとデータに関するビジネスロジックが混在する(FatViewModelになる)
* 依存性をひとまとめにする層が欲しい
* 人によって実装がバラバラで属人化する傾向が..

### VIPERを採用した理由
リリースまでスケジュール的に余裕がないなか、アプリの要件を満たす必要充分なレイヤーがVIPERにあり、しっくりきた。
何よりサンプルを作ってみて、iOS開発していて辛いと感じていた課題を解決できるレイヤーが揃っている点が良くVIPERを採用しました

| 今までの設計に抱いた課題 | VIPERを採用すると     |
| :------------- | :------------- |
| ルーティングが欲しい      | Routerが担保してくれる      |
| Viewのロジックとデータに関するロジックが混在 | Presenter, Interactorで明確に分離されている       |
| 依存性をひとまとめに解決する層がない | Routerがインスタンスを生成し依存性をまとめる役割を担っている(後述) |
| 属人化する | レイヤーが明確なため属人化しづらい |


## VIPERにして良かった点

### 依存性をひとまとめにする層があること
Router層は文字通り、Routingに責務を負いますが、各レイヤーの依存性を解決する役割も担保します。
こんな感じで、staticなメソッドを定義してここからある画面に関連するレイヤーの依存性を解決してViewのインスタンスを取得できます。

``` swift
final class PasswordResetRouter: PasswordResetWireframe {
    weak var viewController: UIViewController?

    required init(viewController: UIViewController) {
        self.viewController = viewController
    }

    // 依存性の解決
    static func assembleModule() -> UIViewController {
        let view = StoryboardScene.PasswordReset.initialScene.instantiate()
        let router = PasswordResetRouter(viewController: view)
        let interactor = PasswordResetInteractor()
        let presenter = PasswordResetPresenter(view: view, router: router, interactor: interactor)
        view.presenter = presenter
        interactor.output = presenter
        return view
    }

    // 普通の画面遷移
    func closeView() {
        self.viewController?.dismiss(animated: true, completion: nil)
    }
}
```

例えばDelegateパターンでDelegateの受け渡しが必要なケースなど、画面間で値渡しが必要なケースで、画面間の依存関係が一目でわかるのは便利です

``` swift
static func assembleModule(with modalBaseView: ModalBaseViewDelegate) -> UIViewController {
    let view = StoryboardScene.LoginView.initialScene.instantiate()
    let router = LoginRouter(viewController: view)
    let interactor = LoginInteractor()
    let presenter = LoginPresenter(view: view, router: router, interactor: interactor)
    view.presenter = presenter
    view.modalBaseViewDelegate = modalBaseView
    interactor.output = presenter
    return view
}
```

### テストが書きやすい
テストに関しては、[単体テストのハジメ](https://speakerdeck.com/yokoyas000/dan-ti-tesutofalsehazime)のスライドを参考にテスト用のスタブとスパイに差し替えてテストする手法を取っているのですが、各レイヤーがInterface(Protocol)にのみ依存しているとそれが簡単にできます。

例えばある機能 `XXX` がない場合、TableViewのセクションが表示されないかどうかテストするコードは下記です。
Interactorの`デバイスにある機能がサポートされているか返すComputedプロパティ`をモックに差し替えて、Viewの構造(TableViewのSectionの数とか)を保持するクラス `viewStructure` が意図した状態になっているかテストしています。

``` swift
class NotAvailableXXXMock: AppSettingsUsecase {
    var output: AppSettingsInteractorOutput!
    var isAvailableXXX: Bool = false
}

class AppSettingsTests: XCTestCase {
    let view = AppSettingsViewController(nibName: nil, bundle: nil)
    var presenter: AppSettingsPresentation!
    var router: AppSettingsRouter!
    
    func testXXXSectionIsHiddenWhenNotAvailableDevice() {
        let mock = NotAvailableXXXMock()
        setupPresenter(with: mock)
        presenter.viewDidLoad() // ここでViewStructorの初期化処理とか書いてる
        
        let sections = presenter.viewStructure.sections
        XCTAssertFalse(sections.contains(.XXX))
    }
    
    private func setupPresenter(with mock: AppSettingsUsecase) {
        presenter = AppSettingsPresenter(view: view, router: router, interactor: mock)
        view.presenter = presenter
    }
}
```

### 後からチームに入った人がすぐ馴染めた
これは感覚値でしかないのですが、僕がチームを抜けたあとJoinしたメンバーが、1回レクチャーをしただけですぐアプリの実装に取りかかれてPRを送ってくれ「分かりやすい」と言ってくれました。
おそらくVIPERがiOS用に作られたアーキテクチャで、程よいレイヤーで必要なポイントを抑えている点が生きたのだと思います。

## 書いてみて工夫した点
### Embedded Frameworkの単位を工夫した
Embedded Frameworkを採用しましたが、構成については画面に依存するレイヤーと依存しないレイヤーで構成を分けることを意識してシンプルにしました。
具体的には、
**画面ごとに必ずセットになるレイヤーView, Presenter, Interactor, Routerはメインのモジュール内に画面ごとにディレクトリを切り、画面に依存しないAPI層, Entity, Utillity, StyleはEmbedded Frameworkとして別モジュールにしました。**
こうすることによって後から入ったメンバーがある画面を改修する際、改修の範囲が視覚的にすぐ認識できて良いです。
<img width="264" alt="スクリーンショット 2018-12-25 1.42.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/30423/e056be33-3ecd-894c-27f8-1621b5426037.png">



### ボイラープレートは自動化した
レイヤードアーキテクチャを採用するとコード量が増えますが、そこはコードジェネレータのKuri(https://github.com/bannzai/Kuri) を使って解決しました。
テンプレを用意しているので、ある画面を開発するときにコマンド1つで先述した1セットが用意できます。

![generated.png](https://qiita-image-store.s3.amazonaws.com/0/30423/12054cea-7ccb-c697-0ec0-2bd4c18c2182.png)


## 反省点

### Viewにモデルを保持したコードを書いてしまっていた
開発の途中というかテストを書くまで、Viewにmutableなモデルを保持したコードを書いてしまっていました。これをやってしまうと、仮にView内でモデルを差し替えたりした場合、Spy, Stubを使ったテストの結果が担保できなくなるのがNGポイントです。
途中からは先輩のコードを参考にViewの構造を管理するStructを作って、Presenterで保持することで、TableViewなどにmutableなModelを持たなくともViewStructureを参照してViewを構築できるようにしました。

``` swift
struct AppSettingsViewStructure {
    private let interactor: AppSettingsUsecase
    private var socialLoginEnabled: Bool
    
    var sections: [Section] {
        return [.user, .logout]
    }
    private(set) var userRows: [UserRow] = {
        return socialLoginEnabled
            ? [.mail, .password, .social]
            : [.mail, .password]
    }()
    
    enum Section {
        case user
        case logout
        
        var title: String? {
            switch self {
            case .user:
                return "アカウント設定"
            case .logout:
                return nil
            }
        }
    }
```

### テストコードがまだ少ない
これはありがちな課題ですが、テストコードがまだ少ないです。実機でモンキーテストすると再現が複雑なケースの一部のみにテストを書いて、必要だと思う箇所にも書けていません。
リリースが落ち着いて、テストで品質を担保するフェーズになったのでテストは増やしたいところ..。実際、テストを書いたところリファクタリングに繋がったこともあったので、リファクタリングがてらやるのが良さそう。

## 参考リンク
実装前に参考にした記事は下記です。最近は国内でもVIPERの記事が増えてきたと思います。

* [iOS Project Architecture: Using VIPER](https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/)
* [Architecting iOS Apps with VIPER](https://www.objc.io/issues/13-architecture/viper/)

## まとめ
1本アプリを作ってみて、それなりの規模と複雑性のあるアプリを仕事で作るなら、今後もVIPERをベースのアーキテクチャに採用するかなぁといった感じです。
アーキテクチャの選定はケースバイケースですが参考になれば幸いです。