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、设置云服务…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
