LoginSignup
6
15

More than 5 years have passed since last update.

MVVMに関して

Last updated at Posted at 2018-01-22

MVCは何

MVCというアーキテクチャーは皆さんに熟知されると思います。MVC具体的にはModel、View、Controller三つの要素から構成されます。簡単に言うと、Modelはデーター層、Viewが表示層、ControllerがUIからの入力を担当します。

iOS開発ではViewControllerという存在がありまして、ViewControllerではViewとController両方の役割が持っています。それで、ViewControllerが複雑になったら、ViewController中でのロジックがどんどん増えて、FatViewControllerになります。FatViewControllerを改修することはかなり大変なことです。MVVMはその問題を解決する方法の一つ。他には色々な方法があるけど、今仕事で使ってるのはMVVMので、詳しく説明します。

MVVMは何

MVVMというアーキテクチャーはModel、View、ViewModel三つの要素で構成されます。Modelはデーター層、Viewが表示層、ViewModelがUIからの入力を受けて、ビューの更新を担当します。MVCのControllerと違うのはViewModelでViewの更新はデータバインディングの手法で実現します。iOS開発では、ViewControllerに対して、ViewModelを作って、うまくデータバインディングしたら、もともとFatViewControllerでデータを処理するロジックを簡単にViewModelに移行することができます。そうしたら、ViewControllerがMVCでのVになれます。

開発手法

実装を書く前に、いくつのルールを説明します。

  1. 一つのViewControllerは一つのViewModelのインスタンスしか持ってません
  2. ViewがViewModelのインスタンスを持ってないけど、プロトコルを利用してデーターバインディングします
  3. 実際の実装ではModelがViewModelに含まれるケースもあります

早速実装手法に関して説明します。データーバインディングはReactiveSwiftというライブラリを使ってやります。

class ViewModel {
    //データーバインディング必要な変数
    let paramA = MutableProperty<Int>(0)
    init(data: [AnyHashable: Any]) {
        if let a = data["paramA"] as? Int {
            paramA.swap(a)
        }
    }
}

class ViewController: UIViewController {
    private let viewModel: ViewModel

    init(data: [AnyHashable: Any]) {
        viewModel = ViewModel(data: data)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        bind(viewModel)
    }

    private func bind(_ viewModel: ViewModel) {
        //データーバインディング
        viewModel.paramA.take(during: reactive.lifetime).observeValue { (value) in
            //paramAが変わったら、この変数を使ってるViewが自動的に更新されます。
        }
    }
}

ViewController初期化する時のデーターを利用してViewModelを初期化します。その後ViewDidLoadでViewModelとViewControllerをバインディングします。

ViewModelのparamAの数値が変わったら、ViewControllerでは自動的にその変化を反映します。

次は子Viewを追加して

protocol AViewModel {
    var paramB: MutableProperty<String> { get }
}
class AView: UIView {
    func bind(_ viewModel: AViewModel) {
        viewModel.paramB.take(during: reactive.lifetime).observeValue{ (value) in
            //paramBを使って、AViewを更新します。
        }   
    }
}

まずAViewというViewを定義して、そしてProtocol型のViewModelを声明します。ここのViewModelがクラス型ではなくProtocol型で声明する理由は後で説明します。続いてはViewModelの修正です。

class ViewModel: AViewModel {
    let paramA = MutableProperty<Int>(0)
    let paramB = MutableProperty<String>("")

    init(data: [AnyHashable: Any]) {
        if let a = data["paramA"] as? Int {
            paramA.swap(a)  
        }
        if let b = data["paramB"] as? String {
            paramA.swap(b)  
        }
    }
}

ViewModelをAViewModelのプロトコルを実装して、ViewControllerにAView型の子Viewを追加して、同時にbind関数を修正します。

class ViewController: UIViewController {
    private let viewModel: ViewModel
    let aView = AView()

    init(data: [AnyHashable: Any]) {
        viewModel = ViewModel(data: data)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubView(aView)
        bind(viewModel)
    }

    private func bind(_ viewModel: ViewModel) {
        //子Viewのデーターバインディング
        aView.bind(viewModel)
        //データーバインディング
        viewModel.paramA.take(during: reactive.lifetime).observeValue { (value) in
            //paramAが変わったら、この変数を使ってるViewを更新します。
        }
    }
}

こうすれば、子ViewとViewModelをデーターバインディングができます。なぜ子ViewのViewModelがクラス型ではなくプロトコル型ですか?このような実装は必ず正しいとは言えませんが、そうする三つの理由を説明します。

  1. 一つのViewControllerに対してデーター処理のロジックはできるだけ一箇所にまとめます もし子ViewのViewModelがデーターを処理するロジックを持っていれば、子Viewと子Viewの間にデーターを更新したい時、その実装はちょっと面倒になります。ロジックが一箇所にまとめたら簡単にできます。でもFatViewModel問題も発生かもしれない。
  2. 子ViewのViewModelがProtocol型にしたら、再利用は簡単です 再利用の場合、この子Viewを持ってるViewControllerのViewModelを子ViewModelを実装すれば完了。Interface向けの考え方はOOP、SwiftではPOP(Protocol Oriented Programing)でもおすすめられます。
  3. メモリー管理がわかりやすくなる データーバインディンに対して、うまく書かないとメモリーリークの恐れがあります*(ViewModelとかViewなど生きたままViewControllerも釈放できません)。一つのViewModelになると、ViewModelのデーターに対して観察の動作をちゃんと終われば、ViewModelを釈放できます。

初めてのqiita投稿、m(..)mよろしくお願いします。

6
15
0

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
6
15