iOSエンジニアリング

[iOSアーキテクチャ #4] MVPとMVVMの違い

iOSアーキテクチャの面接で、よく聞かれる質問があります。

読了 4 分
[iOSアーキテクチャ #4] MVPとMVVMの違いのカバー画像

iOSアーキテクチャの面接で、よく聞かれる質問があります。

「MVPとMVVMの違いは何ですか?」

どちらもビューコントローラをスリム化するパターンで、中央に中間オブジェクト(Presenter/ViewModel)を置きます。構成図だけを見ると、箱の名前が1つ変わっただけに見えるため、意外と答えにくい質問です。

今回は、この2つの違いをたった1つの基準で整理します。**「中間オブジェクトはViewを知っているか、知らないか」**です。


MVP:PresenterがViewに命令する

MVP(Model-View-Presenter)では、PresenterはViewを知っています。正確には、Viewが実装するプロトコル(インターフェース)を参照します。

protocol ProfileViewProtocol: AnyObject {
    func showName(_ name: String)
    func showLoading(_ isLoading: Bool)
}

final class ProfilePresenter {
    weak var view: ProfileViewProtocol?

    func load() {
        view?.showLoading(true)
        // ...データ読み込み後
        view?.showName("山田太郎")
        view?.showLoading(false)
    }
}

流れが見えますか?Presenterがデータを加工した後、Viewに直接「これを表示して」と命令します。View(ビューコントローラ)はプロトコルメソッドを実装し、指示どおりに描画するだけです。

  • メリット:流れが明示的で追いやすく、バインディングフレームワークが不要です。
  • デメリット:画面要素が増えるほど、プロトコルメソッドが増え続けます。showNameshowAgeshowBadge

MVVM:ViewModelはViewの存在を知らない

一方、MVVM(Model-View-ViewModel)のViewModelはViewを参照しません。状態を保持するだけです。

final class ProfileViewModel {
    @Published private(set) var displayName = ""
    @Published private(set) var isLoading = false

    func load() {
        isLoading = true
        // ...データ読み込み後
        displayName = "山田太郎"
        isLoading = false
    }
}

view?.showName(...)のような呼び出しはありません。ViewModelは自分の状態だけを更新し、View側がその状態を購読(バインディング)して自動的に描画します。命令の向きが逆なのです。

  • MVP:Viewへプッシュする
  • MVVM:ViewがViewModelを購読してオブザーブする

この違いにより、MVVMではバインディング手段(Combine、@Observableなど)が実質的に必須です。バインディングのないMVVMが不完全な理由は前回詳しく解説しました。

Presenterはプッシュし、ViewModelは購読される
Presenterはプッシュし、ViewModelは購読される

テストコードを見ると違いが明確になる

どちらのパターンも「画面なしでロジックをテストする」ことが目的ですが、テスト方法は異なります。

MVPのテストでは、偽のViewを作って呼び出しを記録する必要があります。

final class MockView: ProfileViewProtocol {
    var shownName: String?
    func showName(_ name: String) { shownName = name }
    func showLoading(_ isLoading: Bool) {}
}
// presenter.load() 後 mockView.shownName 検証

MVVMのテストは、mockなしで状態の値だけを確認すれば完了です。

let vm = ProfileViewModel()
vm.load()
#expect(vm.displayName == "山田太郎")

プロトコルもmockオブジェクトも必要ありません。ViewModelがViewを知らないからこそ得られる実質的なメリットです。


では、どちらを使うべきか?

基準は意外と単純です。バインディング手段が自然に存在するかです。

  • SwiftUI、またはCombineを使うUIKit → MVVMが自然です。バインディングを無料で使えるからです。
  • バインディング導入の負担が大きいレガシーUIKit → MVPのほうが実用的です。プロトコルだけで動き、流れも明示的なのでオンボーディングも容易です。

近年のiOSでMVVMが事実上の標準になったのは、MVPより優れているからというより、Combineと@Observableがフレームワークレベルで提供され、MVVM唯一の参入障壁だったバインディングがなくなったために近いでしょう。

バインディングを無料で使える環境かどうかが選択基準
バインディングを無料で使える環境かどうかが選択基準

まとめ

  • MVPとMVVMの本質的な違いは1つです。PresenterはView(プロトコル)を知って直接命令し、ViewModelはViewを知らずに状態だけを公開します。
  • そのため、MVPにバインディングは不要ですが、MVVMには必須です。
  • テストでは、MVPにmock Viewが必要なのに対し、MVVMは状態の検証だけで完了します。
  • バインディングを無料で使える環境(SwiftUI・Combine)ならMVVM、そうでなければMVPも十分に良い選択です。

次回は、ここからさらに一歩進んだモジュール分離の極端な形、VIPERを扱います。大規模アプリがなぜ導入し、なぜ離れたのかまで整理します。

あわせて読みたい