63
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-02-20

はじめに

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

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

swift初心者がSmartNews風ニュースアプリを作ってみる過程を晒す(6) - Alamofire + Object Mapper + Realm + SDWebImageで最低限動くニュースアプリを作る - Qiita

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

MVCからMVVMへ

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

MVC in iOS

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

mvc.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の導入を検討することになりました。

MVVM in iOS

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

スクリーンショット 2016-02-20 11.23.04.png

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

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

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

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

MVVMとデータバインディング

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

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

長くなりすぎて心折れそうなので 紙幅の関係上、ここでは割愛させて頂きますが、リアクティブプログラミングやデータバインディングについては、WEB+DB PRESS vol.85で、@hamasyou さんが詳しく解説してくださっています。

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

MVVMのメリット/デメリット

###メリット:

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

###デメリット:

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

サンプルアプリをのぞいてみる

実際にMVVMを使用したサンプルアプリケーションのコードを見てみましょう。
ここでは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アプリはメンテナンスやテストをしやすくなりそうです。

感想

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

おわりに

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

参考

###ReactiveCocoa

###MVVM

63
59
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?