swift初心者がSmartNews風ニュースアプリを作ってみる過程を晒す(7)　- MVVMで関心事を分離する手法を学ぶ


## <i class="fa fa-cube" style="font-size:1em;"></i> はじめに

swiftはほとんど未経験ですが、SmartNews風ニュースアプリを作ってみて、その過程をさらしています。

前回は、こんな記事を書きました。

[swift初心者がSmartNews風ニュースアプリを作ってみる過程を晒す(6) - Alamofire + Object Mapper + Realm + SDWebImageで最低限動くニュースアプリを作る - Qiita](http://qiita.com/tjnet/items/2a51c655cbc1d9b3000b)

今回は、iOSにおけるMVVMアーキテクチャについて考えます。

## <i class="fa fa-cube" style="font-size:1em;"></i> MVCからMVVMへ

最近では、MVCがMVVMに置き換えられる場面が多く見られるようになってきました。ここではその移り変わりについて見ていきます。

## <i class="fa fa-cube" style="font-size:1em;"></i> MVC in iOS

iOSにおける典型的なMVCはこんな感じです。

![mvc.png](https://qiita-image-store.s3.amazonaws.com/0/34321/8773d9b4-ef7e-d5d7-0f10-0a0739c0941e.png)

Modelがdataを保持し、viewがインタラクティブなインタフェースをユーザに提供し、view controllerがuser intaeractionを処理してModelを更新します。


###Massive View Controller

MVCアーキテクチャを Massive View Controllerと呼ぶ人もいます。MVCアーキテクチャにおいて、ViewControllerはmodelとviewの相互作用を仲介するため、肥大化しがちだからです。

ViewControllerが肥大化すれば、そのViewControllerはいくつもの状態を持ちうるようになります。そうなれば、手動テストであれUnit Testであれテストは困難になります。また、ロジックの再利用も困難になります。

したがって、ViewControllerに本来記述すべきでない処理を切り出す必要が出てきます。
このような背景があり、今回、MVVMの導入を検討することになりました。

## <i class="fa fa-cube" style="font-size:1em;"></i> MVVM in iOS

MVCの代わりに、.NETでよく使われるMVVMが出てきました。MVVMは、Model-View-ViewModelの頭文字を取ったものです。

![スクリーンショット 2016-02-20 11.23.04.png](https://qiita-image-store.s3.amazonaws.com/0/34321/fd1af839-db97-84ce-5dde-58079684cc40.png)

このアーキテクチャのコアになるViewModelはUIの状態を保持します( 例えば、text fieldに現在入力されている文字列や、特定のボタンが押下可能になっているかどうか、といった状態を保持しています。)

MVVMでは厳密に守るべきルールがあります。

- ViewはViewModelへの参照を保持します(逆方向への参照は禁止しています)。
- ViewModelはModelへの参照を保持します(逆方向への参照は禁止しています)。

このルールがあることによって、依存性の向きをシンプルに保ち、開発・テスト・メンテナンスが容易になります。

## <i class="fa fa-cube" style="font-size:1em;"></i> MVVMとデータバインディング

ここでひとつ、疑問が浮かんできます。ViewModelがViewへの参照を保持していないとすれば、ViewModelはどうやってViewを更新するのでしょうか？

これを解決するのが、リアクティブプログラミングのデータバインディングという仕組みです。

~~長くなりすぎて心折れそうなので~~ 紙幅の関係上、ここでは割愛させて頂きますが、リアクティブプログラミングやデータバインディングについては、[WEB+DB PRESS vol.85](http://hamasyou.com/blog/2015/02/22/web-plus-db-press-vol-dot-85/)で、@hamasyou さんが詳しく解説してくださっています。

実装していく際には、~~ReactiveCocoa~~ RxSwiftを使用することになりますが、[jalehman/todolist-mvvm: Sample application using MVVM in Swift](https://github.com/jalehman/todolist-mvvm)を見ると、実装の雰囲気がつかめるかもしれません。

## <i class="fa fa-cube" style="font-size:1em;"></i> MVVMのメリット/デメリット

###メリット:

- ViewModelにユーザ入力のvalidationロジック、viewのプレゼンテーションロジックなどを記述することができます。結果として、ViewControllerの肥大化を防ぐことができます。
- View-ViewController componentはModelを直接参照しません。従って、UI部分とビジネスロジックを並行開発することが容易になります。
- ViewModelはViewからViewModelへ、ViewModelからModelへと向かうシンプルな依存関係を持っており、より簡単にUnit Testを記述することが可能となります。

###デメリット:

- ViewModelというレイヤを新たに設けるので、コードの総量は増えそうです。

## <i class="fa fa-cube" style="font-size:1em;"></i>サンプルアプリをのぞいてみる

実際にMVVMを使用したサンプルアプリケーションのコードを見てみましょう。
ここでは[yoichitgy/SwinjectMVVMExample_ForBlog](https://github.com/yoichitgy/SwinjectMVVMExample_ForBlog)を題材にします。まずはViewControllerのコードを見てみます。

```ImageSearchTableViewController.swift
import UIKit
import ExampleViewModel
![スクリーンショット 2016-02-20 11.11.21.png](https://qiita-image-store.s3.amazonaws.com/0/34321/85da4b71-c2d5-09dd-bed7-8cde5634050d.png)

public final class ImageSearchTableViewController: UITableViewController {
    private var autoSearchStarted = false
    
    public var viewModel: ImageSearchTableViewModeling? {
        didSet {
            if let viewModel = viewModel {
                viewModel.cellModels.producer
                    .on(next: { _ in self.tableView.reloadData() })
                    .start()
            }
        }
    }
    
    public override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        if !autoSearchStarted {
            autoSearchStarted = true
            viewModel?.startSearch()
        }
    }
}

// MARK: UITableViewDataSource
extension ImageSearchTableViewController {
    public override func tableView(
        tableView: UITableView,
        numberOfRowsInSection section: Int) -> Int
    {
        if let viewModel = viewModel {
            return viewModel.cellModels.value.count
        }
        return 0
    }
    
    public override func tableView(
        tableView: UITableView,
        cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCellWithIdentifier(
            "ImageSearchTableViewCell",
            forIndexPath: indexPath) as! ImageSearchTableViewCell
        if let viewModel = viewModel {
            cell.viewModel = viewModel.cellModels.value[indexPath.row]
        }
        else {
            cell.viewModel = nil
        }
        
        return cell
    }
}
```

ViewControllerがViewModelのインスタンスを所有しています。
次にViewModelを見てみます。ViewModelは画像の検索やネットワークアクセスを担当しています。

```ImageSearchTableViewModel.swift

import ReactiveCocoa
import ExampleModel

public final class ImageSearchTableViewModel: ImageSearchTableViewModeling {
    public var cellModels: AnyProperty<[ImageSearchTableViewCellModeling]> {
        return AnyProperty(_cellModels)
    }
    private let _cellModels = MutableProperty<[ImageSearchTableViewCellModeling]>([])
    private let imageSearch: ImageSearching
    private let network: Networking
    
    public init(imageSearch: ImageSearching, network: Networking) {
        self.imageSearch = imageSearch
        self.network = network
    }
    
    public func startSearch() {
        imageSearch.searchImages()
            .map { response in
                response.images.map {
                    ImageSearchTableViewCellModel(image: $0, network: self.network)
                        as ImageSearchTableViewCellModeling
                }
            }
            .observeOn(UIScheduler())
            .on(next: { cellModels in
                self._cellModels.value = cellModels
            })
            .start()
    }
}
```

ViewModelにネットワークアクセス機能や画像検索機能が記述されています。ViewControllerがModelを参照することはなくなり、シンプルにViewModelから提供される値を表示します。このように責任を分担することにより、MVVMアーキテクチャのiPhoneアプリはメンテナンスやテストをしやすくなりそうです。

## <i class="fa fa-cube" style="font-size:1em;"></i>感想

体感的には、MVVMを使用するとMVCに比べてコードの記述量は少し増加する気がします。
一方で、コードの複雑性は減少するため、開発の生産性は向上すると思っています。

## <i class="fa fa-cube" style="font-size:1em;"></i>おわりに

次回は、ここまでの知識を踏まえて、実際にMVVMアーキテクチャと~~ReactiveCocoa~~ RxSWiftを使用した実装を行います。

## <i class="fa fa-cube" style="font-size:1em;"></i>参考

###ReactiveCocoa

- [Reactive Swift — Swift Programming — Medium](https://medium.com/swift-programming/reactive-swift-3b6050375534#.z57yixvau)
- [Interlude: ReactiveCocoa](https://github.com/jalehman/todolist-mvvm)
- [ReactiveCocoa/ReactiveViewModel: Model-View-ViewModel, using ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveViewModel)
- [MVVM Tutorial with ReactiveCocoa: Part 1/2 - Ray Wenderlich](http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1)

###MVVM

- [リアクティブプログラミングとMVVMパターンについて - SSSSLIDE](http://sssslide.com/www.slideshare.net/taketin/mvvm-40956834)
- [MVVMをベースに複雑な振る舞いをしっかり把握できるアプリ開発 - Qiita](http://qiita.com/susieyy/items/2af5321b287b8d2f49f6)
- [Introduction to MVVM · objc.io](https://www.objc.io/issues/13-architecture/mvvm/)
- [Model-View-ViewModel for iOS | Teehan+Lax](http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/)
- [ReactiveCocoaとMVVMモデル - Qiita](http://qiita.com/nghialv/items/0c2fee3890cb671a597c)
- [Let’s discuss MVVM for iOS — Medium](https://medium.com/@ramshandilya/lets-discuss-mvvm-for-ios-a7960c2f04c7#.ebemb1qtu)
- [MVVM Tutorial with ReactiveCocoa: Part 1/2 - Ray Wenderlich](http://www.raywenderlich.com/74106/mvvm-tutorial-with-reactivecocoa-part-1)