iOS开发架构——MVC、MVP和MVVM对比
文章目录
- 前言
- MVC(Model - View - Controller)
- MVP(Model - View - Presenter)
- MVVM(Model - View - ViewModel)
前言
在 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 inself?.nameLabel.text = name}.store(in: &cancellables)}@objc func loadUserTapped() {viewModel.loadUser()}
}
分析:
- 数据绑定调试困难(数据变化自动触发 UI,数据流是隐式的,调试时很难知道“谁更新了谁”)
- 不适合所有页面(小页面 + 简单逻辑,用 MVC 或 MVP 更高效)
- 双向绑定滥用风险(状态混乱或循环更新)
- ViewModel 可能过于庞大(Massive ViewModel)
ViewModel 承担了很多职责,如果不合理拆分,容易臃肿,等同于从 ViewController 搬家而已。
- 接收用户输入
- 做业务逻辑
- 暴露 UI 状态
- 响应用户行为