当前位置: 首页 > news >正文

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,但:

  1. View 仍需手动更新 UI,Presenter 获取数据后,需要调用 View 协议来“告诉”它更新 UI:
// ViewController 实现这些更新方法,很容易随着页面变复杂而变臃肿。
view?.showUserName(user.name)
  1. 通信是被动式的,如果状态很多(例如 name、age、头像等),Presenter 需要逐个通知,View 也要逐个处理。

MVVM(Model - View - ViewModel)

特点:

  • 引入绑定机制(数据驱动 UI)
MVVM 中,ViewModel 暴露可观察属性(如 Swift 的 @PublishedRxSwiftObservableUI 层通过绑定,一旦数据变化就自动刷新,不需要手动调用更新方法。
  • 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 状态
  • 响应用户行为
http://www.xdnf.cn/news/297955.html

相关文章:

  • 如何开始使用 Blender:Blender 3D 初学者指南和简介 怎么下载格式模型
  • java springboot deepseek流式对话集成示例
  • UE5 材质淡入淡出
  • 【数据结构】求有向图强连通分量的方法
  • 【开发工具】Window安装WSL及配置Vscode获得Linux开发环境
  • 虚拟现实视频播放器 2.6.1 | 支持多种VR格式,提供沉浸式观看体验的媒体播放器
  • Spark,HDFS客户端操作
  • mysql中select 1 from的作用
  • 博客系统测试报告
  • 在命令行终端中快速打开npm包官网
  • MySQL从入门到精通(二):Windows和Mac版本MySQL安装教程
  • 【STM32项目实战】一文了解单片机的SPI驱动外设功能
  • (十)深入了解AVFoundation-采集:录制视频功能的实现
  • HTTP 与 HTTPS 的深度剖析:差异、原理与应用场景
  • Day17 聚类算法(K-Means、DBSCAN、层次聚类)
  • MacOS+VSCODE 安装esp-adf详细流程
  • Three.js和WebGL区别、应用建议
  • 【奔跑吧!Linux 内核(第二版)】第1章:Linux 系统基础知识
  • 【测试开发】概念篇 - 从理解需求到认识常见开发、测试模型
  • 第二节:Vben Admin 最新 v5.0 对接后端登录接口(上)
  • 用OMS从MySQL迁移到OceanBase,字符集utf8与utf8mb4的差异
  • 如何保障服务器租用中的数据安全?
  • 基于 Trae 的单细胞 RNA 测序分析与可视化
  • Linux下的好玩的命令
  • Linux:进程间通信---命名管道共享内存
  • Android组件化 -> Debug模式下,本地构建module模块的AAR和APK
  • Nginx安全防护与HTTPS部署
  • 如何搭建spark yarn模式集群的集群
  • OpenKylin安装Elastic Search8
  • 多线程“CPU 飙高”问题:如何确保配置的线程数与CPU核数匹配(Java、GoLang、Python )中的最佳实践解决方案