iOS 架构设计 mvvm mvp mvc

4 阅读6分钟

在 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() { /* 布局代码省略 */ }
}

总结与对比

特性MVCMVPMVVM
核心思想Controller 作为中介,协调 View 和 ModelPresenter 完全接管业务逻辑,View 被动ViewModel 提供数据状态,View 自动响应
依赖关系Controller 强依赖 View 和 ModelPresenter 依赖 View 协议和 ModelView 依赖 ViewModel,ViewModel 依赖 Model
View 与逻辑层关系Controller 持有 View 和 ModelPresenter 持有 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 的单元测试非常方便。