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.mzph.cn/web/78721.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

0506--01-DA

36. 单选题 在娱乐方式多元化的今天&#xff0c;“ ”是不少人&#xff08;特别是中青年群体&#xff09;对待戏曲的态度。这里面固然存在 的偏见、难以静下心来欣赏戏曲之美等因素&#xff0c;却也有另一个无法回避的原因&#xff1a;一些戏曲虽然与观众…

关于Java多态简单讲解

面向对象程序设计有三大特征&#xff0c;分别是封装&#xff0c;继承和多态。 这三大特性相辅相成&#xff0c;可以使程序员更容易用编程语言描述现实对象。 其中多态 多态是方法的多态&#xff0c;是通过子类通过对父类的重写&#xff0c;实现不同子类对同一方法有不同的实现…

【Trea】Trea国际版|海外版下载

Trea目前有两个版本&#xff0c;海外版和国内版。‌ Trae 版本差异 ‌大模型选择‌&#xff1a; ‌国内版‌&#xff1a;提供了字节自己的Doubao-1.5-pro以及DeepSeek的V3版本和R1版本。海外版&#xff1a;提供了ChartGPT以及Claude-3.5-Sonnet和3.7-Sonnt. ‌功能和界面‌&a…

Missashe考研日记-day33

Missashe考研日记-day33 1 专业课408 学习时间&#xff1a;2h30min学习内容&#xff1a; 今天开始学习OS最后一章I/O管理的内容&#xff0c;听了第一小节的内容&#xff0c;然后把课后习题也做了。知识点回顾&#xff1a; 1.I/O设备分类&#xff1a;按信息交换单位、按设备传…

链表的面试题3找出中间节点

来来来&#xff0c;接着继续我们的第三道题 。 解法 暴力求解 快慢指针 https://leetcode.cn/problems/middle-of-the-linked-list/submissions/ 这道题的话&#xff0c;思路是非常明确的&#xff0c;就是让你找出我们这个所谓的中间节点并且输出。 那这道题我们就需要注意…

linux磁盘介绍与LVM管理

一、磁盘基本概述 GPT是全局唯一标识分区表的缩写,是全局唯一标示磁盘分区表格式。而MBR则是另一种磁盘分区形式,它是主引导记录的缩写。相比之下,MBR比GPT出现得要更早一些。 MBR 与 GPT MBR 支持的磁盘最大容量为 2 TB,GPT 最大支持的磁盘容量为 18 EB,当前数据盘支持…

突破测试环境文件上传带宽瓶颈!React Native 阿里云 OSS 直传文件格式问题攻克二

上一篇我们对服务端和阿里云oss的配置及前端调用做了简单的介绍&#xff0c;但是一直报错。最终判断是文件格式问题&#xff0c;通常我们在reactnative中用formData上传&#xff0c; formData.append(file, {uri: file, name: nameType(type), type: multipart/form-data});这…

Spring Boot 中 @Bean 注解详解:从入门到实践

在 Spring Boot 开发中&#xff0c;Bean注解是一个非常重要且常用的注解&#xff0c;它能够帮助开发者轻松地将 Java 对象纳入 Spring 容器的管理之下&#xff0c;实现对象的依赖注入和生命周期管理。对于新手来说&#xff0c;理解并掌握Bean注解&#xff0c;是深入学习 Spring…

TCP 协议设计入门:自定义消息格式与粘包解决方案

目录 一、为什么需要自定义 TCP 协议&#xff1f; TCP粘包问题的本质 1.1 粘包与拆包的定义 1.2 粘包的根本原因 1.3 粘包的典型场景 二、自定义消息格式设计 2.1 协议结构设计 方案1&#xff1a;固定长度协议 方案2&#xff1a;分隔符标记法 方案3&#xff1a;长度前…

了解一下OceanBase中的表分区

OceanBase 是一个高性能的分布式关系型数据库&#xff0c;它支持 SQL 标准的大部分功能&#xff0c;包括分区表。分区表可以帮助管理大量数据&#xff0c;提高查询效率&#xff0c;通过将数据分散到不同的物理段中&#xff0c;可以减少查询时的数据扫描量。 在 OceanBase 中操…

多线程网络编程:粘包问题、多线程/多进程服务器实战与常见问题解析

多线程网络编程&#xff1a;粘包问题、多线程/多进程服务器实战与常见问题解析 一、TCP粘包问题&#xff1a;成因、影响与解决方案 1. 粘包问题本质 TCP是面向流的协议&#xff0c;数据传输时没有明确的消息边界&#xff0c;导致多个消息可能被合并&#xff08;粘包&#xf…

大模型主干

1.什么是语言模型骨架LLM-Backbone,在多模态模型中的作用&#xff1f; 语言模型骨架&#xff08;LLM Backbone&#xff09;是多模态模型中的核心组件之一。它利用预训练的语言模型&#xff08;如Flan-T5、ChatGLM、UL2等&#xff09;来处理各种模态的特征&#xff0c;进行语义…

[创业之路-350]:光刻机、激光器、自动驾驶、具身智能:跨学科技术体系全景解析(光-机-电-材-热-信-控-软-网-算-智)

光刻机、激光器、自动驾驶、具身智能四大领域的技术突破均依赖光、机、电、材、热、信、控、软、网、算、智十一大学科体系的深度耦合。以下从技术原理、跨学科融合、关键挑战三个维度展开系统性分析&#xff1a; 一、光刻机&#xff1a;精密制造的极限挑战 1. 核心技术与学科…

SVTAV1 编码函数 svt_aom_is_pic_skipped

一 函数解释 1.1 svt_aom_is_pic_skipped函数的作用是判断当前图片是否可以跳过编码处理。 具体分析如下 函数逻辑 参数说明&#xff1a;函数接收一个指向图片父控制集的指针PictureParentControlSet *pcs, 通过这个指针可以获取与图片相关的各种信息&#xff0c;用于判断是否跳…

【Redis新手入门指南】从小白入门到日常使用(全)

文章目录 前言redis是什么&#xff1f;定义原理与特点与MySQL对比 Redis安装方式一、Homebrew 快速安装 Redis&#xff08;推荐&#xff09;方式二、源码编译安装redisHomebrew vs 源码安装对比 redis配置说明修改redis配置的方法常见redis配置项说明 redis常用命令redis服务启…

Linux grep 命令详解及示例大全

文章目录 一、基本语法二、常用选项及示例1. 基本匹配&#xff1a;查找包含某字符串的行2. 忽略大小写匹配 -i3. 显示行号 -n4. 递归查找目录下的文件 -r 或 -R5. 仅显示匹配的字符串 -o6. 使用正则表达式 -E&#xff08;扩展&#xff09;或 egrep7. 显示匹配前后行 -A, -B, -C…

【排序算法】快速排序(全坤式超详解)———有这一篇就够啦

【排序算法】——快速排序 目录 一&#xff1a;快速排序——思想 二&#xff1a;快速排序——分析 三&#xff1a;快速排序——动态演示图 四&#xff1a;快速排序——单趟排序 4.1&#xff1a;霍尔法 4.2&#xff1a;挖坑法 4.3&#xff1a;前后指针法 五&#xff1a;…

【platform push 提示 Invalid source ref: HEAD】

platform push 提示 Invalid source ref: HEAD 场景&#xff1a;环境&#xff1a;排查过程&#xff1a;解决&#xff1a; 场景&#xff1a; 使用platform push 命令行输入git -v 可以输出git 版本号&#xff0c;但就是提示Invalid source ref: HEAD&#xff0c;platform creat…

x-cmd install | Tuistash - Logstash 实时监控,告别图形界面,高效便捷!

目录 核心优势&#xff0c;一览无遗安装适用场景&#xff0c;广泛覆盖功能亮点&#xff0c;不容错过 还在为 Logstash 的监控而头疼吗&#xff1f;还在频繁切换图形界面查看数据吗&#xff1f;现在&#xff0c;有了 Tuistash&#xff0c;一切都将变得简单高效&#xff01; Tui…

【JEECG】BasicTable单元格编辑,插槽添加下拉组件样式错位

1.功能说明 BasicTable表格利用插槽&#xff0c;添加组件实现单元格编辑功能&#xff0c;选择组件下拉框错位 2.效果展示 3.解决方案 插槽内组件增加&#xff1a;:getPopupContainer"getPopupContainer" <template #salesOrderProductStatus"{ column, re…