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、设置云服务…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...