在 iOS 开发中,MVC、MVP、MVVM 是三种核心的架构设计模式。它们的核心目标都是分离关注点,将界面展示(View)、业务逻辑(Controller/Presenter/ViewModel)和数据(Model)解耦,以提高代码的可测试性、可维护性和复用性。
下面我将分别阐述它们的核心区别,并通过 Swift 代码示例来展示具体实现。
1. MVC (Model-View-Controller)
MVC 是 iOS 开发中最基础、最传统的架构。Apple 的 UIKit 本身就是基于 MVC 设计的。
核心区别与特点
- View:负责展示界面,接收用户交互(如点击、滑动)。
- Controller:作为“中介者”,持有 Model 和 View,负责处理业务逻辑、更新 Model、刷新 View,以及响应 View 的事件。
- Model:负责数据存储和业务规则(如网络请求、数据库操作)。
在 iOS 中的问题:由于 View 的生命周期和事件响应通常由 Controller 管理,导致 ViewController 往往过于臃肿(被称为“Massive View Controller”),包含了视图控制、数据解析、网络请求、导航等大量代码,难以测试和维护。
代码实现
swift
import UIKit
// MARK: - Model (数据模型)
struct User {
let name: String
let age: Int
}
// MARK: - View (视图,通常由 Storyboard 或 Xib 创建,这里用代码简单示意)
class UserView: UIView {
let nameLabel = UILabel()
let ageLabel = UILabel()
let updateButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
// 布局代码省略...
nameLabel.text = "Name: "
ageLabel.text = "Age: "
updateButton.setTitle("Update", for: .normal)
}
required init?(coder: NSCoder) { fatalError() }
func display(user: User) {
nameLabel.text = "Name: (user.name)"
ageLabel.text = "Age: (user.age)"
}
}
// MARK: - Controller (视图控制器,承担了太多职责)
class UserViewController: UIViewController {
private var user: User = User(name: "Alice", age: 25) // 持有 Model
private var customView = UserView() // 持有 View
override func viewDidLoad() {
super.viewDidLoad()
view = customView
customView.updateButton.addTarget(self, action: #selector(updateButtonTapped), for: .touchUpInside)
updateUI()
}
// 业务逻辑:修改数据并更新视图
@objc func updateButtonTapped() {
// 模拟业务逻辑 (这里本应由 Model 层处理,但直接写在了 Controller)
user = User(name: "Bob", age: 30)
updateUI()
}
private func updateUI() {
customView.display(user: user)
}
}
2. MVP (Model-View-Presenter)
MVP 是为了解决 MVC 中 Controller 臃肿的问题而出现的。它将业务逻辑从 ViewController 中抽离出来,放入 Presenter 中。
核心区别与特点
- View:只负责展示界面和接收用户交互,并将交互事件传递给 Presenter。View 是被动的。
- Presenter:持有 Model 和 View 的引用(通过协议与 View 交互,实现解耦)。它包含所有的 UI 业务逻辑,决定何时更新 Model 以及如何刷新 View。
- Model:数据层。
与 MVC 的不同:View 和 Model 不再直接通信,完全通过 Presenter 传递。ViewController 属于 View 层(充当 View 的角色),变得更“瘦”。
代码实现
swift
import UIKit
// MARK: - Model
struct User {
let name: String
let age: Int
}
// MARK: - View 协议 (定义 View 需要实现的能力)
protocol UserViewProtocol: AnyObject {
func showUser(name: String, age: String)
func showLoadingIndicator()
func hideLoadingIndicator()
}
// MARK: - Presenter (核心业务逻辑层)
class UserPresenter {
private weak var view: UserViewProtocol? // 弱引用避免循环引用
private var user: User
init(view: UserViewProtocol, user: User) {
self.view = view
self.user = user
}
// 当 View 加载完成时调用
func onViewDidLoad() {
view?.showLoadingIndicator()
// 模拟业务处理(如格式化数据)
updateView()
view?.hideLoadingIndicator()
}
// 处理更新按钮点击
func onUpdateButtonTapped() {
// 业务逻辑:更新 Model
user = User(name: "Charlie", age: 35)
updateView()
}
private func updateView() {
view?.showUser(name: user.name, age: "(user.age) years")
}
}
// MARK: - View (ViewController 充当 View 角色)
class UserViewController: UIViewController, UserViewProtocol {
private var presenter: UserPresenter?
private let nameLabel = UILabel()
private let ageLabel = UILabel()
private let updateButton = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
// 初始化 Presenter
let initialUser = User(name: "Alice", age: 25)
presenter = UserPresenter(view: self, user: initialUser)
presenter?.onViewDidLoad()
updateButton.addTarget(self, action: #selector(didTapUpdate), for: .touchUpInside)
}
// MARK: - UserViewProtocol 实现
func showUser(name: String, age: String) {
nameLabel.text = "Name: (name)"
ageLabel.text = "Age: (age)"
}
func showLoadingIndicator() {
print("Show loading...") // 实际项目中可展示 UIActivityIndicatorView
}
func hideLoadingIndicator() {
print("Hide loading...")
}
@objc private func didTapUpdate() {
presenter?.onUpdateButtonTapped() // 将用户事件传递给 Presenter
}
private func setupUI() { /* 布局代码省略 */ }
}
3. MVVM (Model-View-ViewModel)
MVVM 在 iOS 中非常流行,尤其是在引入 Combine 或 RxSwift 等响应式编程框架后。它的核心是 数据绑定(Data Binding),让 View 自动响应 ViewModel 的变化。
核心区别与特点
- View:持有 ViewModel,并观察 ViewModel 的变化。当数据变化时,View 自动更新。
- ViewModel:持有 Model,负责将 Model 的数据转换为 View 可以直接展示的格式(如将
Date转为String),并处理 View 触发的命令。 - Model:数据层。
- View 与 ViewModel 的通信:通过观察者模式(KVO、Combine、Closure、Delegate)实现双向绑定或单向绑定。
与 MVP 的不同:Presenter 持有 View 的引用并主动调用 View 的方法;而 ViewModel 不持有 View 的引用,View 通过观察 ViewModel 的属性来被动更新。
代码实现 (使用 Swift 的 @Published 和 ObservableObject 实现数据绑定)
swift
import UIKit
import Combine
// MARK: - Model
struct User {
let name: String
let age: Int
}
// MARK: - ViewModel (负责数据转换和业务逻辑)
class UserViewModel: ObservableObject {
// 使用 @Published 发布变化,View 可以订阅
@Published var displayName: String = ""
@Published var displayAge: String = ""
@Published var isLoading: Bool = false
private var user: User {
didSet {
updateDisplayData()
}
}
init(user: User) {
self.user = user
updateDisplayData()
}
// 处理 View 的更新动作
func updateUser() {
isLoading = true
// 模拟异步业务逻辑(如网络请求)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.user = User(name: "David", age: 40)
self?.isLoading = false
}
}
// 将 Model 数据转换为展示数据
private func updateDisplayData() {
displayName = "Name: (user.name)"
displayAge = "Age: (user.age)"
}
}
// MARK: - View (ViewController)
class UserViewController: UIViewController {
private var viewModel: UserViewModel
private var cancellables = Set<AnyCancellable>()
private let nameLabel = UILabel()
private let ageLabel = UILabel()
private let updateButton = UIButton(type: .system)
private let activityIndicator = UIActivityIndicatorView(style: .medium)
init(viewModel: UserViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
bindViewModel()
updateButton.addTarget(self, action: #selector(didTapUpdate), for: .touchUpInside)
}
// 绑定 ViewModel 数据到 View
private func bindViewModel() {
// 使用 Combine 订阅 ViewModel 的变化
viewModel.$displayName
.receive(on: DispatchQueue.main)
.assign(to: .text, on: nameLabel)
.store(in: &cancellables)
viewModel.$displayAge
.receive(on: DispatchQueue.main)
.assign(to: .text, on: ageLabel)
.store(in: &cancellables)
viewModel.$isLoading
.receive(on: DispatchQueue.main)
.sink { [weak self] isLoading in
if isLoading {
self?.activityIndicator.startAnimating()
} else {
self?.activityIndicator.stopAnimating()
}
}
.store(in: &cancellables)
}
@objc private func didTapUpdate() {
viewModel.updateUser() // 触发 ViewModel 的业务逻辑
}
private func setupUI() { /* 布局代码省略 */ }
}
总结与对比
| 特性 | MVC | MVP | MVVM |
|---|---|---|---|
| 核心思想 | Controller 作为中介,协调 View 和 Model | Presenter 完全接管业务逻辑,View 被动 | ViewModel 提供数据状态,View 自动响应 |
| 依赖关系 | Controller 强依赖 View 和 Model | Presenter 依赖 View 协议和 Model | View 依赖 ViewModel,ViewModel 依赖 Model |
| View 与逻辑层关系 | Controller 持有 View 和 Model | Presenter 持有 View 协议 | View 持有 ViewModel,ViewModel 不持有 View |
| 数据流向 | 双向,Controller 负责传递 | 双向,Presenter 负责传递 | 单向数据流(响应式),View 观察 ViewModel |
| 适合场景 | 简单页面、系统原生组件 | 需要将 ViewController 瘦身的复杂页面 | 复杂交互、多数据源、需要高度可测试的场景 |
| 测试难度 | 困难(Controller 与 UIKit 耦合) | 容易(Presenter 独立于 UIKit) | 容易(ViewModel 独立于 UIKit) |
| iOS 实现方式 | 直接使用 UIKit 的 Target-Action、Delegate | 通过协议定义 View 接口,Presenter 调用 | 使用 @Published、Combine、RxSwift、Closure 回调 |
如何选择?
- MVC:适用于非常简单的模块,或者你完全能控制住 ViewController 不变得臃肿的情况。
- MVP:如果你不想引入响应式框架的复杂性,又想严格将业务逻辑从 ViewController 中剥离出来,MVP 是一个清晰的选择。
- MVVM:目前 iOS 社区的主流选择。配合 Combine(Swift 原生)或 RxSwift,能够实现非常优雅的数据绑定,极大地减少样板代码,并且 ViewModel 的单元测试非常方便。