MOFASHY

Live Is Life

软件设计模式之MVC、MVP、MVVM、MVA、MVI

软件架构设计是将软件一些共有的特征转换为满足业务需求和技术要求的结构化方案的过程。软件架构设计的目的是为了实现系统的长期可维护性、可扩展性、可靠性和安全性,以满足业务需求和未来可能的变化。

在过去的几年里,软件架构的设计模式层出不穷,每一个模式的出现,都是试图想使代码更可读、更容易于测试、更易于维护以及让开发人员更轻松。但同时,每个模式的出现也都有适应的场景和优劣。

本文主要对MVC、MVP、MVVM、MVA、MVI五个常见的设计模式做一个汇总的介绍。

一、五种软件架构模式的详解和对比

1、MVC(Model-View-Controller)

  • 核心结构:分为模型(数据与业务逻辑)、视图(用户界面)、控制器(协调层)。
  • 数据流:View → Controller → Model → View,但Model可直接更新View导致耦合。
  • 优点:职责分离简单,适合小型项目。
  • 缺点:Controller易臃肿,View与Model未完全解耦。

2、MVP(Model-View-Presenter)

  • 改进点:引入Presenter替代Controller,完全隔离View与Model,通过接口通信。
  • 数据流:View → Presenter → Model → Presenter → View,单向且解耦。
  • 优点:便于单元测试,View可复用。
  • 缺点:接口数量随业务增长膨胀,Presenter可能内存泄漏。

3、MVVM(Model-View-ViewModel)

  • 关键特性:ViewModel通过双向绑定(如LiveData)自动同步View和Model,减少模板代码。
  • 数据流:双向绑定实现View与Model实时交互。
  • 优点:开发效率高,适合数据驱动型UI。
  • 缺点:调试复杂,过度绑定可能引发性能问题。

4、MVI(Model-View-Intent)

  • 设计思想:严格单向数据流,用户意图(Intent)触发状态(State)更新,View渲染不可变状态。
  • 数据流:View → Intent → Model → State → View,强调状态管理。
  • 优点:状态可预测,利于复杂交互场景。
  • 缺点:状态膨胀可能增加内存开销。

5、MVA(Model-View-Adapter)

  • 改进点:引入Adapter,完全隔离View与Model,类似MVP但更轻量。
  • 数据流:Adapter桥接Model与View,处理双向数据绑定‌。
  • 优点:简化View逻辑,适合数据个复杂的场景。
  • 缺点:Adapter可能成为新的复杂度来源。

对比总结

架构 核心特点 解耦程度 数据流 适用场景 典型问题
MVC Controller中介,View与Model可能耦合 部分解耦 双向/部分解耦 小型项目 简单应用,快速开发
MVP Presenter手动同步,高解耦 完全解耦 单向 中大型项目 需要高测试性的项目
MVVM 数据绑定,ViewModel解耦 高度解耦 双向绑定 复杂UI,数据驱动应用 调试困难
MVI 单向数据流,状态不可变 完全解耦 严格单向 响应式或状态密集型应用 状态管理成本高
MVA Adapter转换数据格式 完全解耦 双向绑定 数据展示逻辑复杂的场景 Adapter可能成为新的复杂度来源

各架构演进反映了对解耦、测试性和状态管理的持续优化。实际选择需权衡项目规模、团队经验及技术栈支持。

二、五种软件架构模式在iOS开发中的应用


1、MVC


模型层(Model)实现

模型负责数据存储和业务逻辑,通常定义为独立类或结构体。例如用户登录场景中,可创建LandModel类管理账户数据,包含初始化方法和数据接口。模型应完全独立于UI,通过属性封装数据,并提供方法处理业务规则(如数据验证或网络请求)。

视图层(View)构建

视图由UIKit组件(如UILabel、UIButton)或自定义UIView子类构成,仅负责界面呈现和用户输入响应。例如登录界面中的文本框和按钮应通过XIB或代码创建,但避免在视图中直接处理业务逻辑。视图通过IBOutlet与控制器连接,或通过addTarget绑定用户交互事件。

控制器层(Controller)协调

控制器继承UIViewController,负责管理视图生命周期并协调模型与视图的交互。典型实现包括:

  • 数据传递:控制器从模型获取数据并格式化后传递给视图显示;
  • 事件处理:响应用户操作(如按钮点击),调用模型方法更新数据,再刷新视图;
  • 通信机制:通过委托模式、通知中心或KVO实现跨层通信,例如模型数据变更时通知控制器更新UI。

模块间通信示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Model
class UserModel {
var name: String
func updateName(_ newName: String) {
name = newName
NotificationCenter.default.post(name: .nameUpdated, object: nil)
}
}

// ViewController
class ProfileVC: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
var user = UserModel()

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: .nameUpdated, object: nil)
}

@objc func updateUI() {
nameLabel.text = user.name
}
}

优化建议

  • 减少控制器臃肿:将数据解析、网络请求等逻辑移至模型层;
  • 视图复用:通过自定义视图类封装重复UI组件;
  • 测试性:因模型独立于UI,可单独进行单元测试。

通过这种分层设计,MVC模式能有效提升代码可维护性,尤其适合中小型iOS应用。对于更复杂场景,可考虑结合MVVM等衍生模式。

2、MVP


2-1、核心组件划分

Model层

定义模型数据(如User类)和业务逻辑(如网络请求),保持与UIKit无关‌。

1
2
3
4
struct User {
var name: String
func fetchData() { /* 网络请求逻辑 */ }
}

View层

  • 视图协议:通过协议(如LoginViewDelegate)定义视图行为,避免直接操作Model。
  • 视图实现UIViewUIViewControoler仅处理UI渲染,通过闭包或KVO通知Presenter更新。
1
2
3
4
5
6
7
protocol LoginViewDelegate: AnyObject {
func loginWith(username: String, password: String)
}
class LoginView: UIView {
weak var delegate: LoginViewDelegate?
// 控件初始化及事件绑定
}

Presenter层

作为桥梁协调View与Model,处理业务逻辑并更新视图。

1
2
3
4
5
6
7
8
9
class LoginPresenter {
let model = User()
weak var view: LoginView?
func login(username: String, password: String) {
model.fetchData { [weak self] result in
self?.view?.delegate?.loginWith(username: username, password: password)
}
}
}

2-2、通信机制

  • 协议回调:View通过协议将用户操作传递给Presenter。
  • 弱引用:使用weak避免循环引用,防止内存泄漏。

2-3、项目结构优化

推荐按功能模块划分目录

1
2
3
4
├── Models/  
├── Views/
├── Presenters/
└── Services/(网络/数据库)

此结构便于团队协作和代码复用。

2-4、优势与注意事项

  • 优势
    • 视图逻辑与业务逻辑完全分离,提升可测试性;
    • 支持多端复用(如iPad和Mac)。
  • 注意事项
    • 避免Presenter过度臃肿,复杂逻辑可拆分为子模块;
    • 使用@MainActor确保UI线程安全。

通过合理应用MVP,可构建高内聚、低耦合的iOS应用,尤其适合需要显式解耦的中大型项目‌。

3、MVVM


3-1、核心组件划分

Model层

定义数据模型(如User类),包含属性和业务逻辑(如网络请求),保持与UIKit无关‌。

1
2
3
4
class User {
var name: String
func fetchData() { /* 网络请求 */ }
}

View层

  • SwiftUI:通过@State和@Binding与ViewModel交互,自动响应数据变化‌。
    1
    2
    3
    4
    struct UserView: View {
    @StateObject private var viewModel = UserViewModel()
    var body: some View { Text(viewModel.user.name) }
    }
  • UIKit:通过KVO或闭包监听ViewModel数据更新‌。

ViewModel层

作为桥梁处理视图逻辑,通过ObservableObject@Published实现数据驱动更新‌。

1
2
3
4
class UserViewModel: ObservableObject {
@Published var user = User()
func saveUser() { user.name = "New Name" }
}

3-2、双向绑定实现

  • SwiftUI‌:利用@StateObject@EnvironmentObject自动同步数据‌。
  • UIKit‌:通过KVO监听属性变化,或使用第三方库(如RxSwift)实现响应式编程‌。

3-3、项目结构:

推荐按功能模块划分目录:

1
2
3
4
├── Models/  
├── Views/
├── ViewModels/
└── Services/(网络/数据库)

此结构便于团队协作和代码复用‌。

3-4、优势与注意事项

  • 优势‌
    • 视图逻辑与业务逻辑分离,提升可测试性‌;
    • 支持多端复用(如iPad和Mac)‌。
  • 注意事项‌
    • 避免ViewModel过度臃肿,复杂逻辑可拆分为子模块‌;
    • 使用@MainActor确保UI线程安全‌。

通过合理应用MVVM,可构建高内聚、低耦合的iOS应用,尤其适合数据驱动型场景‌。

4、MVI


4-1、核心组件设计

  • Model(State):使用不可变结构体(如Swift的struct)描述UI状态,包含加载状态、数据集合及错误信息等字段,确保线程安全且状态变化可追踪。例如:
1
2
3
4
5
struct ArticleState {
var isLoading: Bool
var articles: [Article]
var error: String?
}
  • View‌:通过UIViewController或SwiftUI的View订阅ViewModel输出的状态流,根据状态渲染UI,并将用户操作(如按钮点击)转换为Intent事件。
  • Intent‌:定义枚举类型表示用户操作(如.loadArticles.refresh),由View传递给ViewModel处理。

4-2、响应式数据流实现

  • 使用Combine框架或RxSwift建立单向数据流:
    1. Intent处理‌:ViewModel接收Intent后触发业务逻辑(如网络请求),生成新State。
    2. 状态更新‌:State通过@Published(Combine)或BehaviorSubject(RxSwift)推送至View。
    3. UI渲染‌:View通过sinksubscribe监听State变化,自动更新界面。

4-3、与iOS原生组件集成

  • UITableView/UICollectionView‌:在自定义Cell中绑定State数据,通过Diffable Data Source实现高效列表更新。
  • SwiftUI兼容‌:MVI天然适配SwiftUI的声明式语法,通过@ObservedObject@StateObject连接ViewModel。

4-4、优势与注意事项

  • 优势‌:状态可预测性强,便于调试;通过单一State简化复杂UI逻辑。
  • 注意事项‌:需合理划分State粒度,避免过度渲染;对于简单场景可能引入冗余代码。

完整示例可参考鸿蒙MVI的实现思路,其响应式状态管理与iOS生态高度契合。实际开发中可结合SwiftUI+Combine实现更简洁的MVI架构。

5、MVA


5-1、核心组件职责

  • Model‌:负责数据管理和业务逻辑,如网络请求或数据库操作‌;
  • ‌View‌:仅处理UI展示和用户交互,不直接操作Model‌;
  • ‌Adapter‌:作为桥梁,将Model数据转换为View可用的格式,并处理双向数据绑定‌。

5-2、实现步骤


示例代码结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Model层
struct UserModel {
let name: String
let age: Int
}

// View层
class UserView: UIView {
let nameLabel = UILabel()
let ageLabel = UILabel()

func update(with user: UserModel) {
nameLabel.text = user.name
ageLabel.text = "Age: \(user.age)"
}
}

// Adapter层
class UserAdapter {
let view: UserView
let model: UserModel

init(view: UserView, model: UserModel) {
self.view = view
self.model = model
view.update(with: model)
}

func updateModel(newModel: UserModel) {
model = newModel
view.update(with: newModel)
}
}
关键点
  • ‌数据绑定‌:Adapter监听Model变化并更新View,反之亦然‌;
  • ‌解耦‌:View通过Adapter间接操作Model,避免直接依赖‌。

5-3、与MVC/MVVM对比

  • ‌MVC‌:Controller易臃肿,View可能依赖Model‌;
  • ‌MVVM‌:依赖响应式框架(如RAC),适合复杂数据流;
  • ‌MVA‌:轻量级,适合中等复杂度项目,无需额外框架‌。

5-4、适用场景

  • 需要简化Controller逻辑的中小型项目‌;
  • 团队对响应式编程不熟悉时,MVA更易维护‌。

通过合理划分职责,MVA能有效提升iOS代码的可读性和可测试性‌。