iOS 工程

[iOS 架構 #4] MVP 與 MVVM 的差異

iOS 架構面試中,有一道經常出現的問題。

閱讀 3 分鐘
[iOS 架構 #4] MVP 與 MVVM 的差異 封面圖

iOS 架構面試中,有一道經常出現的問題。

「MVP 和 MVVM 有什麼差異?」

兩者都是讓檢視控制器更精簡的模式,也都在中間放入中介物件(Presenter/ViewModel)。只看架構圖,頂多像是其中一個方塊換了名稱,因此其實不太容易回答。

今天就用一個標準整理兩者的差異:「中介物件是否知道 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 反而更實用。只靠協定就能運作,流程明確,也容易讓新人上手。

近年 MVVM 幾乎成為 iOS 的標準,與其說是因為它勝過 MVP,不如說是因為Combine 和 @Observable 在框架層級提供,讓 MVVM 唯一的進入門檻(繫結)消失了

選擇關鍵在於環境是否免費提供繫結
選擇關鍵在於環境是否免費提供繫結

總結

  • MVP 和 MVVM 的本質差異只有一個。Presenter 知道 View(協定)並直接下指令;ViewModel 不知道 View,只公開狀態。
  • 所以 MVP 不需要繫結,而 MVVM 則必須繫結。
  • 在測試中,MVP 需要 mock View,MVVM 只要驗證狀態即可完成。
  • 如果環境免費提供繫結(SwiftUI・Combine),就選 MVVM;否則 MVP 也是非常好的選擇。

下一篇將介紹更進一步的模組分離極端案例:VIPER,也會整理大型 App 為何導入它,以及為何後來離開它。

延伸閱讀