iOSでMVVM(バインディングを使わない)

  • 34
    いいね
  • 0
    コメント

はじめに

iOSでMVVMアーキテクチャを採用した開発に取り組んでいます。
MVVMと言っても、RxSwiftReactiveKitのようにReactive Programmingを実現するためのフレームワークは用いずに、単純な形にすることを目標に考えた方法について記載します。

こちらの記事 MVVM: A non-reactive introductionの内容を参考にさせていただいております。

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を実現するには、先に述べたRxSwiftReactiveKitなどのフレームワークを導入したり、バインディング機構を自作するという方法があります。

個人的には、独特な記述が要求される、このようなフレームワークの導入にはあまり気がのりません。
独特な記述によって余計に複雑になったり、ロックインされてしまってもしもの時に移行しにくいというリクスもあると思うからです。

Cocoaだと、Cocoa-Bindingがサポートされていますが、残念ながらiOSにはCocoa-Bindingがサポートされていません。

バインディングを捨てる、単純にViewModel層を導入する

MVVMは、データバインディングによってデータとビューを宣言的に結び付けられるところが良いところの一つですが、バインディングしなくともViewModelは導入できます。

値の更新は、自分で記述する必要がありますが、ViewControllerの肥大化を防げたり、Viewの操作とビジネスロジック部分をわけやすくなるなどのメリットは十分にあります。また今から述べる方法だとアプリケーション全体をMVVMにする必要はなく、一部の分離したいところを分離するといったこともやりやすいと考えています。

全体像

Screen Shot 2016-12-10 at 15.45.39.png

View, ViewModel, ViewModelStateを使います。ViewControllerは、Viewに含みます。

ViewModelState

ViewModelが取り得る状態を表します。

enum **ViewModelState {
    case state1, state2, ...
}

ViewModel

stateDidUpdateというプロパティを持ち、ViewModelStateを渡して、Viewに更新を通知します。

ViewModelというプロトコルを作り、各種ViewModelクラスを定義します。

ViewModel.swift
protocol ViewModel {
    associatedtype State
    var stateDidUpdate: ((State) -> Void)? { get set }
}
**ViewModel.swift
class **ViewModel: ViewModel {

    var stateDidUpdate: ((**ViewModelState) -> Void)?

    ...

    func updateToState1() {
        ....
        self.stateDidUpdate?(.state1)
    }
}

View

ViewModelを持っていて、viewDidLoadviewModelstateDidUpdateを設定し、viewModelの更新を受け取ります。

**ViewController.swift
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
一度見てみてください。