iOS开发设计模式篇第二篇MVVM设计模式
目录
一、什么是MVVM
二、MVVM 的主要特点
三、MVVM 的架构图
四、MVVM 与其他模式的对比
五、如何在iOS中实现MVVM
1.Model
2.ViewModel
3.View (ViewController)
4.双向绑定
5.文中完整的代码地址
六、MVVM 的优缺点
1.优点
2.缺点
七、MVVM 的应用场景
八、结语
在iOS开发中,设计模式对于提升代码的可维护性、可读性和扩展性有着重要作用。其中,MVVM (Model-View-ViewModel) 是一种流行的架构模式,它通过引入 ViewModel 层解决了 View 和 Model 耦合过高的问题。本文将详细介绍MVVM的基本概念、实现原理以及在iOS中的实际应用。
一、什么是MVVM
MVVM 是一种分层架构,将应用分为以下三个部分:
-
Model(数据层):负责存储和处理数据。它可以是业务逻辑对象、数据库模型或网络响应对象。
-
View(视图层):负责显示 UI,并响应用户交互。
-
ViewModel(视图模型层):负责将 Model 转化为 View 可以使用的数据,同时接收 View 的操作并更新 Model。
通过 ViewModel,View 和 Model 之间不再直接通信,从而降低了耦合性。
二、MVVM 的主要特点
数据绑定:通过绑定机制实现 View 和 ViewModel 的双向通信。
单一职责:Model、View 和 ViewModel 各自专注于自己的职责。
易于测试:ViewModel 的逻辑独立于 UI,便于单元测试。
三、MVVM 的架构图
View <-----> ViewModel <-----> Model
- View:只关心如何展示数据,通常使用 UIKit 或 SwiftUI 构建。
- ViewModel:充当中间层,负责从 Model 获取数据,并将其转换为适合展示的数据格式。
- Model:负责处理底层数据逻辑,如网络请求或数据库操作。
四、MVVM 与其他模式的对比
| 特性 | MVC | MVVM |
| 数据绑定 | 无 | 有 |
| View 和 Model 耦合 | 高耦合 | 松耦合 |
| 测试 | 较难测试 | 易于测试 |
| 学习成本 | 低 | 中等 |
在小型项目中,MVC 可能更加轻便;但在大型项目中,MVVM 可以显著提升代码的可维护性。
五、如何在iOS中实现MVVM
以下我们通过一个简单的用户信息显示例子来演示如何使用 MVVM。
最终的效果图如下:

图1.mvvm的例子
1.Model
在本文的例子中,Model表示示例中使用到的数据模型,即用户名和年龄。
import Foundation
// MARK: - Model
class UserModel: NSObject {@objc dynamic var name: String@objc dynamic var age: Intinit(name: String, age: Int) {self.name = nameself.age = age}
}
2.ViewModel
ViewModel中充当View和Model的中间层。它有两个作用。
1.获取Model中的数据并把它转成View中要使用的数据格式。
在本文的代码中,我们提供一个初始化的方法,把Model中的数据转成要展示的name和age属性,同时我们通过KVO的方式监听UserModel中的变化,实时获取变化之后的最新值。
第二个提供一个方法接收View的操作并且更新Model。
import Foundation
// MARK: - ViewModel
class UserInfoViewModel: NSObject {@objc dynamic var name: String@objc dynamic var age: Intprivate var user: UserModelinit(user: UserModel) {self.user = userself.name = user.nameself.age = user.agesuper.init()// 观察 Model 的变化并同步到 ViewModelself.user.addObserver(self, forKeyPath: #keyPath(UserModel.name), options: [.new], context: nil)self.user.addObserver(self, forKeyPath: #keyPath(UserModel.age), options: [.new], context: nil)}deinit {// 移除观察者user.removeObserver(self, forKeyPath: #keyPath(UserModel.name))user.removeObserver(self, forKeyPath: #keyPath(UserModel.age))}override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {guard let keyPath = keyPath else { return }switch keyPath {case #keyPath(UserModel.name):if let newName = change?[.newKey] as? String {name = newName}case #keyPath(UserModel.age):if let newAge = change?[.newKey] as? Int {age = newAge}default:break}}func updateUser(name: String, age: Int) {user.name = nameuser.age = age}
}
3.View (ViewController)
这里View的指的是View和UIViewController。我们把UIView和UIViewController都看做事MVVM设计模式中的View。它的作用是和ViewModel通信。
import UIKit
import IFLYCommonKit
import Foundationclass UserInfoViewController: IFLYCommonBaseVC {private var viewModel: UserInfoViewModel!private let nameLabel = UILabel()private let ageLabel = UILabel()override func viewDidLoad() {super.viewDidLoad()setupUI()let user = UserModel(name: "John", age: 25)viewModel = UserInfoViewModel(user: user)bindViewModel()}private func setupUI() {view.addSubview(nameLabel)view.addSubview(ageLabel)nameLabel.frame = CGRect(x: 20, y: 100, width: 200, height: 30)ageLabel.frame = CGRect(x: 20, y: 150, width: 200, height: 30)}private func bindViewModel() {nameLabel.text = viewModel.displayNameageLabel.text = viewModel.displayAge}
}
4.双向绑定
如果需要双向绑定,可以引入第三方库如 Combine 或 RxSwift。
这里使用KVO实现。
import UIKit
import IFLYCommonKit
import Foundationclass UserInfoViewController: IFLYCommonBaseVC {private var viewModel = UserInfoViewModel(user: UserModel(name: "unknown", age: 0))// UI 元素private let nameLabel: UILabel = {let label = UILabel()label.font = UIFont.systemFont(ofSize: 18)label.textColor = .blackreturn label}()private let ageLabel: UILabel = {let label = UILabel()label.font = UIFont.systemFont(ofSize: 16)label.textColor = .darkGrayreturn label}()private let updateButton: UIButton = {let button = UIButton(type: .system)button.setTitle("Update Info", for: .normal)button.backgroundColor = .systemBluebutton.setTitleColor(.white, for: .normal)button.layer.cornerRadius = 8return button}()// MARK: - Lifecycleoverride func viewDidLoad() {super.viewDidLoad()setupUI()setupBindings()}// MARK: - Setup UIprivate func setupUI() {title = "MVVM设计模式"view.backgroundColor = .whiteview.addSubview(nameLabel)view.addSubview(ageLabel)view.addSubview(updateButton)// SnapKit 布局nameLabel.snp.makeConstraints { make inmake.centerX.equalToSuperview()make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(20)}ageLabel.snp.makeConstraints { make inmake.centerX.equalToSuperview()make.top.equalTo(nameLabel.snp.bottom).offset(10)}updateButton.snp.makeConstraints { make inmake.centerX.equalToSuperview()make.top.equalTo(ageLabel.snp.bottom).offset(20)make.width.equalTo(150)make.height.equalTo(40)}updateButton.addTarget(self, action: #selector(updateUserInfo), for: .touchUpInside)}// MARK: - Bindingsprivate func setupBindings() {// KVO 绑定viewModel.addObserver(self, forKeyPath: "name", options: [.new, .initial], context: nil)viewModel.addObserver(self, forKeyPath: "age", options: [.new, .initial], context: nil)}override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {if keyPath == "name", let newName = change?[.newKey] as? String {nameLabel.text = "Name: \(newName)"} else if keyPath == "age", let newAge = change?[.newKey] as? Int {ageLabel.text = "Age: \(newAge)"}}deinit {viewModel.removeObserver(self, forKeyPath: "name")viewModel.removeObserver(self, forKeyPath: "age")}// MARK: - Actions@objc private func updateUserInfo() {// 随机更新用户信息let randomNames = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace"]let newName = randomNames.randomElement() ?? "Unknown"let newAge = Int.random(in: 18...60)viewModel.updateUser(name: newName, age: newAge)}
}
5.文中完整的代码地址
文章篇幅有限,懒人直接点击这里获取。
六、MVVM 的优缺点
1.优点
1. 低耦合:View 和 Model 解耦,便于扩展。
2. 易测试:ViewModel 不依赖 UI,测试更容易。
3. 代码复用性强:ViewModel 可以在多个 View 中复用。
2.缺点
1. 初始学习成本较高。
2. 对于小型项目可能显得过于复杂。
七、MVVM 的应用场景
1. 中大型项目:适合复杂的数据流和界面逻辑。
2. 需要数据绑定的项目:如表单输入、列表数据展示。
3. 跨平台项目:如同时支持 iOS 和 macOS 的项目,ViewModel 可以很容易地复用。
八、结语
MVVM 是一种强大的设计模式,在 iOS 开发中,特别是复杂的应用程序中,它能显著提高代码的可维护性和扩展性。通过正确地分层设计,你可以更轻松地应对不断变化的需求。
相关文章:
iOS开发设计模式篇第二篇MVVM设计模式
目录 一、什么是MVVM 二、MVVM 的主要特点 三、MVVM 的架构图 四、MVVM 与其他模式的对比 五、如何在iOS中实现MVVM 1.Model 2.ViewModel 3.View (ViewController) 4.双向绑定 5.文中完整的代码地址 六、MVVM 的优缺点 1.优点 2.缺点 七、MVVM 的应用场景 八、结…...
【深度学习】3.损失函数的作用
损失函数的作用 假设把猫这张图片分成四个像素点,分别为:56、231、24、2(实际应该是三维的,因为还有颜色通道的维度,这里简化成二维)。 像素点拿到以后,进行三分类,粉红色为第一组W…...
深入MapReduce——计算模型设计
引入 通过引入篇,我们可以总结,MapReduce针对海量数据计算核心痛点的解法如下: 统一编程模型,降低用户使用门槛分而治之,利用了并行处理提高计算效率移动计算,减少硬件瓶颈的限制 优秀的设计,…...
小黑日常积累:学习了CROSS APPLY字段,将sqlserver中字段通过分隔符拆分并统计
问题 字段中的元素是通过分隔符进行拼接的,我需要统计元素的个数,例如: 代码 样例表创建 -- 创建样例表 create table #Tmp_Table (ID int IDENTITY (1,1) not null,Strs nvarchar(50),primary key (ID) ); insert into #Tmp_Table (Strs) VALUES…...
WebSocket知识点笔记(一)
WebSocket WebSocket是一种在单个TCP连接上进行全双工通信的协议。它使得客户端和服务端之间的消息传递更加高效,允许服务器主动向客户端推送数据。 一.WebSocket全双工通信 WebSocket提供了真正的双向通信,客户端和服务端可以同时发送和接收消息 …...
安宝特方案 | AR在供应链管理中的应用:提升效率与透明度
随着全球化的不断深入和市场需求的快速变化,企业对供应链管理的要求也日益提高。如何在复杂的供应链环境中提升效率、降低成本,并确保信息的透明度,成为了各大行业亟待解决的问题。而增强现实(AR)技术,特别…...
基于Springboot + vue实现的美发门店管理系统
💖学习知识需费心, 📕整理归纳更费神。 🎉源码免费人人喜, 🔥码农福利等你领! 💖常来我家多看看, 📕网址:扣棣编程, 🎉感谢支持常陪伴, 🔥点赞关注别忘记! 💖山高路远坑又深, 📕大军纵横任驰奔, 🎉谁敢横刀立马行? 🔥唯有点赞+关注成! �…...
springboot中配置logback-spring.xml
一、在src/main/resources目录下,也就是在classpath路径下创建logback-spring.xml 注:springboot框架自动配置,如果更换名称,可在配置文件指定该文件即可 <?xml version"1.0" encoding"UTF-8"?> <…...
从63 秒到 0.482 秒:深入剖析 MySQL 分页查询优化
在日常开发中,数据库查询性能问题就像潜伏的“地雷”,总在高并发或数据量庞大的场景下引爆。尤其是当你运行一条简单的分页查询时,结果却让用户苦苦等待,甚至拖垮了系统。这种情况你是否遇到过? 你可能会想࿱…...
细说机器学习算法之过拟合与欠拟合
系列文章目录 第一章:Pyhton机器学习算法之KNN 第二章:Pyhton机器学习算法之K—Means 第三章:Pyhton机器学习算法之随机森林 第四章:Pyhton机器学习算法之线性回归 第五章:Pyhton机器学习算法之有监督学习与无监督…...
C/C++ 虚函数
虚函数的定义 虚函数是指在基类内部声明的成员函数前面添加关键字 virtual 指明的函数虚函数存在的意义是为了实现多态,让派生类能够重写(override)其基类的成员函数派生类重写基类的虚函数时,可以添加 virtual 关键字,但不是必须这么做虚函…...
【3GPP】【5G】注销流程(Deregistration procedures)
1. 欢迎大家订阅和关注,精讲3GPP通信协议(2G/3G/4G/5G/IMS)知识点,专栏会持续更新中.....敬请期待! 目录 3.1.2 Deregistration procedures 3.1.2.1 UE-initiated Deregistration 3.1.2.2 Network-initiated Deregistration 3.1.2 Deregistration procedures 注销流程…...
【小游戏篇】三子棋游戏
硬控我一上午,小编还是太菜了,大家可以自行升级电脑难度,也可以升级游戏到五子棋 1.game.h #pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 3 #define COL 3//初始化棋盘 void InitBoa…...
7-Zip Mark-of-the-Web绕过漏洞复现(CVE-2025-0411)
免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…...
2025年国产化推进.NET跨平台应用框架推荐
2025年国产化推进.NET跨平台应用框架推荐 1. .NET MAUI NET MAUI是一个开源、免费(MIT License)的跨平台框架(支持Android、iOS、macOS 和 Windows多平台运行),是 Xamarin.Forms 的进化版,从移动场景扩展到…...
关于ARM和汇编语言
一图流 ARM 计算机组成 输入设备 输出设备 存储设备 运算器 控制器 处理器读取内存程序执行的过程 取指阶段:控制器器通过地址总线向存储器发送想要获取的指令的地址编号,存储器将指定的指令发送给处理器 译码阶段:控制器对指令进行分…...
2024人工智能AI+制造业应用落地研究报告汇总PDF洞察(附原数据表)
原文链接: https://tecdat.cn/?p39068 本报告合集洞察深入剖析当前技术应用的现状,关键技术 创新方向,以及行业应用的具体情况,通过制造业具体场景的典型 案例揭示人工智能如何助力制造业研发设计、生产制造、运营管理 和产品服…...
QTableView和QTableWidget的关系与区别
QTableView 和 QTableWidget 都是 Qt 框架中用于显示表格数据的控件,但它们在设计和使用上有一些重要的区别。 QTableView 模型-视图架构:QTableView 是 Qt 模型-视图架构的一部分,它与模型(如 QStandardItemModel 或自定义的 QA…...
Java导出通过Word模板导出docx文件并通过QQ邮箱发送
一、创建Word模板 {{company}}{{Date}}服务器运行情况报告一、服务器:总告警次数:{{ServerTotal}} 服务器IP:{{IPA}},总共告警次数:{{ServerATotal}} 服务器IP:{{IPB}},总共告警次数:{{ServerBTotal}} 服务器IP:{{IPC}}&#x…...
ESP8266 MQTT服务器+阿里云
MQTT私有平台搭建(EMQX 阿里云) 阿里云服务器 EMQX 搭建私有MQTT平台 1、搜索EMQX开源版本 2、查看各版本EMQX支持的UBUNTU版本 3、查看服务器Ubuntu版本 4、使用APT安装模式 5、按照官网指示安装并启动 6、下载安装MQTTX测试工具 7、设置云服务…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
mcts蒙特卡洛模拟树思想
您这个观察非常敏锐,而且在很大程度上是正确的!您已经洞察到了MCTS算法在不同阶段的两种不同行为模式。我们来把这个关系理得更清楚一些,您的理解其实离真相只有一步之遥。 您说的“select是在二次选择的时候起作用”,这个观察非…...
记一次spark在docker本地启动报错
1,背景 在docker中部署spark服务和调用spark服务的微服务,微服务之间通过fegin调用 2,问题,docker容器中服务器来后,注册中心都有,调用服务也正常,但是调用spark启动任务后报错,报错…...
