はじめに
iOSでMVVMアーキテクチャを採用した開発に取り組んでいます。
MVVMと言っても、RxSwiftやReactiveKitのようにReactive Programmingを実現するためのフレームワークは用いずに、単純な形にすることを目標に考えた方法について記載します。
こちらの記事 [MVVM: A non-reactive introduction] (https://medium.com/@IanKeen/mvvm-a-non-reactive-introduction-5a1f33402a32#.kok2bhglh)の内容を参考にさせていただいております。
iOSはMVC
iOSのアプリケーションアーキテクチャには、MVCが採用されています。
Model-View-Controller - Apple Developer
そのため、特殊なことをしなければ、それぞれのクラスはModel, View, Controller層に分かれアプリケーションが構成されます。
しかし、MVCでアプリを開発していると、次第にViewControllerが肥大化していくという問題は、誰もが通る道かと思います。
その問題を解決すべく、様々なアーキテクチャが提案されています。
iOS Architecture Patterns
まだMVC,MVP,MVVMで消耗してるの? iOS Clean Architectureについて
MVVM
MVVMについての説明は省略しますが、もともとはWPFやSilverlight, Windows Store AppなどMicrosoft系の開発において採用されているアーキテクチャです。
MVVM (Model-View-ViewModel) - Wikipedia
MVVMで重要になるのが、データバインディングですが、XAMLによってデータバインディングがプラットフォームレベルで可能になっているため、Microsoft系の開発ではMVVMを採用しやすくなっています。
iOSにMVVMを導入すると、ViewControllerの肥大化を防げたり、Viewの操作とビジネスロジック部分をわけやすくなるため、全体の構成がわかりやすくなります。
しかし、最初に述べたように、iOSはそもそもMVCアーキテクチャなので、単純にはMVVMアーキテクチャにできません。
フレームワーク?
iOSでMVVMを実現するには、先に述べたRxSwiftやReactiveKitなどのフレームワークを導入したり、バインディング機構を自作するという方法があります。
個人的には、独特な記述が要求される、このようなフレームワークの導入にはあまり気がのりません。
独特な記述によって余計に複雑になったり、ロックインされてしまってもしもの時に移行しにくいというリクスもあると思うからです。
Cocoaだと、Cocoa-Bindingがサポートされていますが、残念ながらiOSにはCocoa-Bindingがサポートされていません。
バインディングを捨てる、単純にViewModel層を導入する
MVVMは、データバインディングによってデータとビューを宣言的に結び付けられるところが良いところの一つですが、バインディングしなくともViewModelは導入できます。
値の更新は、自分で記述する必要がありますが、ViewControllerの肥大化を防げたり、Viewの操作とビジネスロジック部分をわけやすくなるなどのメリットは十分にあります。また今から述べる方法だとアプリケーション全体をMVVMにする必要はなく、一部の分離したいところを分離するといったこともやりやすいと考えています。
全体像
View, ViewModel, ViewModelStateを使います。ViewControllerは、Viewに含みます。
ViewModelState
ViewModelが取り得る状態を表します。
enum **ViewModelState {
case state1, state2, ...
}
ViewModel
stateDidUpdate
というプロパティを持ち、ViewModelStateを渡して、Viewに更新を通知します。
ViewModelというプロトコルを作り、各種ViewModelクラスを定義します。
protocol ViewModel {
associatedtype State
var stateDidUpdate: ((State) -> Void)? { get set }
}
class **ViewModel: ViewModel {
var stateDidUpdate: ((**ViewModelState) -> Void)?
...
func updateToState1() {
....
self.stateDidUpdate?(.state1)
}
}
View
ViewModelを持っていて、viewDidLoad
でviewModel
のstateDidUpdate
を設定し、viewModel
の更新を受け取ります。
class **ViewController: UIViewController {
var viewModel: **ViewModel!
...
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.stateDidUpdate = { [unowned self] state in
switch state {
case .state1:
....
case .state2:
....
}
}
}
}
サンプル
サンプルプロジェクトをGitHubに作成しています。
NoBindingMVVMSample
一度見てみてください。