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。我们还会总结大型应用为何采用它,以及为何最终放弃它。

延伸阅读