前言
在 iOS 开发中,MVC、MVVM、和 MVP 是常见的三种架构模式,它们主要目的是解耦视图与业务逻辑,提高代码复用性和可维护性。下面我将通过一个简单的示例「展示用户名」来解释它们的区别,并配上对应代码。
假设场景:
- 有一个 User 模型,包含昵称 name
- UI 包含一个 UILabel 显示名字,一个 UIButton 模拟点击“加载用户”
- 点击按钮 → 加载用户数据 → 显示用户昵称
MVC(Model - View - Controller)
特点:
- Controller 是桥梁:连接 Model 和 View
- View 很「傻」,Controller 很「胖」(逻辑全堆里面)
// Model
struct User {
var name: String
}
// ViewController 充当 Controller 和 View 的职责
class MVCViewController: UIViewController {
let nameLabel = UILabel()
let loadButton = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI() {
nameLabel.frame = CGRect(x: 50, y: 100, width: 200, height: 30)
loadButton.frame = CGRect(x: 50, y: 150, width: 100, height: 40)
loadButton.setTitle("加载用户", for: .normal)
loadButton.addTarget(self, action: #selector(loadUser), for: .touchUpInside)
view.addSubview(nameLabel)
view.addSubview(loadButton)
}
// 模拟点击:在 Controller 中处理逻辑
@objc func loadUser() {
let user = User(name: "小明") // 模拟从后端获取
nameLabel.text = user.name // 更新 UI
}
}
在 iOS 的 MVC 架构中,ViewController 集中包含了 View 和 Controller 的逻辑,这是由 Apple 官方 UIKit 框架的设计风格所造成的,而不是 MVC 理论的本意。
UIKit 组件(如 UIViewController)本身就是既负责控制逻辑(Controller),又默认持有和管理 UI(View)的类。比如:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 控制逻辑(Controller)
// 创建和管理 UI(View)
let label = UILabel()
label.text = "Hello"
self.view.addSubview(label)
}
}
UIViewController.view 就是一个默认的视图容器。所以,View 和 Controller 就混在一起用了。
所以很多人吐槽 iOS MVC 是 “Massive View Controller”,代码都堆在 VC 里,不利于维护。
MVP(Model - View - Presenter)
在 iOS 的 MVC 中,ViewController 既负责 UI,又负责业务逻辑。随着功能增加,代码会变得:
- 臃肿(Massive ViewController)
- 难以测试
- 难以复用
MVP 将业务逻辑(如获取数据、处理点击事件)抽到 Presenter 中,ViewController 只关心 UI。
特点:
- Presenter 处理业务逻辑(可以单元测试),不依赖 UIKit
- View 是一个协议,由 ViewController 实现(View 使用协议抽象,可以 mock View,可以单元测试)
- 更适合做单元测试
// Model
struct User {
var name: String
}
// View 协议:只关心显示逻辑
protocol UserView: AnyObject {
func displayUserName(_ name: String)
}
// Presenter:负责处理业务逻辑
class UserPresenter {
weak var view: UserView?
init(view: UserView) {
self.view = view
}
func fetchUser() {
let user = User(name: "小红") // 模拟数据获取
view?.displayUserName(user.name)
}
}
// ViewController 实现 View 协议
class MVPViewController: UIViewController, UserView {
let nameLabel = UILabel()
let loadButton = UIButton(type: .system)
var presenter: UserPresenter!
override func viewDidLoad() {
super.viewDidLoad()
presenter = UserPresenter(view: self)
setupUI()
}
func setupUI() {
nameLabel.frame = CGRect(x: 50, y: 100, width: 200, height: 30)
loadButton.frame = CGRect(x: 50, y: 150, width: 100, height: 40)
loadButton.setTitle("加载用户", for: .normal)
loadButton.addTarget(self, action: #selector(loadUserTapped), for: .touchUpInside)
view.addSubview(nameLabel)
view.addSubview(loadButton)
}
@objc func loadUserTapped() {
presenter.fetchUser()
}
func displayUserName(_ name: String) {
nameLabel.text = name
}
}
虽然 MVP 已经将业务逻辑抽离到了 Presenter,但:
- View 仍需手动更新 UI,Presenter 获取数据后,需要调用 View 协议来“告诉”它更新 UI:
// ViewController 实现这些更新方法,很容易随着页面变复杂而变臃肿。
view?.showUserName(user.name)
- 通信是被动式的,如果状态很多(例如 name、age、头像等),Presenter 需要逐个通知,View 也要逐个处理。
MVVM(Model - View - ViewModel)
特点:
- 引入绑定机制(数据驱动 UI)
MVVM 中,ViewModel 暴露可观察属性(如 Swift 的 @Published 或 RxSwift 的 Observable)
UI 层通过绑定,一旦数据变化就自动刷新,不需要手动调用更新方法。
- ViewController 更轻量,不再关心如何显示,而是“绑定好”UI 到 ViewModel
viewModel.$name.sink { self.nameLabel.text = $0 }
- 状态管理更统一,ViewModel 通常以“状态集合”的方式存在,能更好地组织
@Published var isLoading: Bool
@Published var name: String
@Published var errorMessage: String?
import Combine
// Model
struct User {
var name: String
}
// ViewModel:处理数据逻辑和 UI 映射
class UserViewModel: ObservableObject {
@Published var username: String = ""
func loadUser() {
let user = User(name: "小花")
username = user.name // 触发 UI 更新
}
}
// ViewController
class MVVMViewController: UIViewController {
let nameLabel = UILabel()
let loadButton = UIButton(type: .system)
var viewModel = UserViewModel()
var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
bindViewModel()
}
func setupUI() {
nameLabel.frame = CGRect(x: 50, y: 100, width: 200, height: 30)
loadButton.frame = CGRect(x: 50, y: 150, width: 100, height: 40)
loadButton.setTitle("加载用户", for: .normal)
loadButton.addTarget(self, action: #selector(loadUserTapped), for: .touchUpInside)
view.addSubview(nameLabel)
view.addSubview(loadButton)
}
func bindViewModel() {
viewModel.$username
.receive(on: RunLoop.main)
.sink { [weak self] name in
self?.nameLabel.text = name
}
.store(in: &cancellables)
}
@objc func loadUserTapped() {
viewModel.loadUser()
}
}
分析:
- 数据绑定调试困难(数据变化自动触发 UI,数据流是隐式的,调试时很难知道“谁更新了谁”)
- 不适合所有页面(小页面 + 简单逻辑,用 MVC 或 MVP 更高效)
- 双向绑定滥用风险(状态混乱或循环更新)
- ViewModel 可能过于庞大(Massive ViewModel)
ViewModel 承担了很多职责,如果不合理拆分,容易臃肿,等同于从 ViewController 搬家而已。
- 接收用户输入
- 做业务逻辑
- 暴露 UI 状态
- 响应用户行为