はじめに
swiftはほとんど未経験ですが、SmartNews風ニュースアプリを作ってみて、その過程をさらしています。
前回は、こんな記事を書きました。
今回は、iOSにおけるMVVMアーキテクチャについて考えます。
MVCからMVVMへ
最近では、MVCがMVVMに置き換えられる場面が多く見られるようになってきました。ここではその移り変わりについて見ていきます。
MVC in iOS
iOSにおける典型的なMVCはこんな感じです。
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の頭文字を取ったものです。
このアーキテクチャのコアになる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のコードを見てみます。
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は画像の検索やネットワークアクセスを担当しています。
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
- Reactive Swift — Swift Programming — Medium
- Interlude: ReactiveCocoa
- ReactiveCocoa/ReactiveViewModel: Model-View-ViewModel, using ReactiveCocoa
- MVVM Tutorial with ReactiveCocoa: Part 1/2 - Ray Wenderlich
###MVVM