文章目錄
- 前言
- 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 狀態
- 響應用戶行為