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、设置云服务…...
鸿蒙系统微博应用锁常见问题解答
为微博设置应用锁后,不少用户会有各种疑问:忘记密码怎么办?会不会影响消息推送?能不能只锁定某些功能?应用锁耗电吗?本文将针对这些高频问题逐一解答,帮助您更好地使用鸿蒙系统(Harm…...
终极鼠标连点器使用指南:3分钟掌握高效自动化技巧
终极鼠标连点器使用指南:3分钟掌握高效自动化技巧 【免费下载链接】MouseClick 🖱️ MouseClick 🖱️ 是一款功能强大的鼠标连点器和管理工具,采用 QT Widget 开发 ,具备跨平台兼容性 。软件界面美观 ,操作…...
6款高效降AI率工具 改写实力出众
写论文时反复检测出的AI痕迹总让你提心吊胆?别担心,这里整理了6款真正好用的论文降AI率工具,堪称应对AI生成特征的“得力助手”。它们能有效识别并消除AI生成的痕迹,改写能力出众,帮你快速降低查重率,顺利通…...
Spring Security OAuth2 /oauth/token 401原因与Content-Type规范
1. 问题现场还原:一个看似简单却让开发停摆两小时的/oauth/token请求刚接手一个老项目做安全加固,第一件事就是验证OAuth2密码模式的token获取流程。我照着文档写了一条curl命令:curl -X POST http://localhost:8080/oauth/token回车执行&…...
defx.nvim 安装与配置完全教程:从零开始搭建高效文件管理系统 [特殊字符]
defx.nvim 安装与配置完全教程:从零开始搭建高效文件管理系统 🚀 【免费下载链接】defx.nvim :file_folder: The dark powered file explorer implementation for neovim/Vim8 项目地址: https://gitcode.com/gh_mirrors/de/defx.nvim defx.nvim …...
2026这6款神级降AIGC平台大公开,一键让AIGC率直逼绝对安全线!
步入 2026 年,学术圈的风向早已不是从前的模样。曾经大家还在为查重率发愁,如今却陷入了更棘手的困境——如何在不破坏论文专业性的前提下,彻底消除 AI 痕迹?随着 AIGC 检测技术不断进化,高校对论文的审核标准也愈发严…...
基于Arduino UNO的真随机数生成与数据持久化在Tambola游戏机中的应用
1. 项目概述:用Arduino UNO打造一台全自动Tambola游戏机如果你玩过或者听说过Tambola(在印度非常流行的游戏,在欧美也叫Bingo或Housie),就知道它的核心玩法是主持人从一个装有数字球的容器中随机抽取号码,玩…...
CA-CFAR、GO-CFAR、SO-CFAR怎么选?一张图看懂三种恒虚警检测算法的适用场景与避坑指南
CA-CFAR、GO-CFAR、SO-CFAR工程选型指南:从算法原理到场景适配 雷达信号处理工程师常常面临一个经典难题:在复杂环境中如何选择合适的恒虚警检测算法?当海面杂波、多目标干扰或低信噪比条件同时出现时,CA、GO、SO三种CFAR变体的性…...
终极虚拟显示器解决方案:ParsecVDisplay完整使用指南
终极虚拟显示器解决方案:ParsecVDisplay完整使用指南 【免费下载链接】parsec-vdd ✨ Perfect virtual display for game streaming 项目地址: https://gitcode.com/gh_mirrors/pa/parsec-vdd ParsecVDisplay是一个基于Parsec虚拟显示驱动(VDD)的独立应用程序…...
如何用嘎嘎降AI处理金融学论文:金融学毕业论文降AI4.8元完整操作教程
如何用嘎嘎降AI处理金融学论文:金融学毕业论文降AI4.8元完整操作教程 第一次用降AI工具有很多不确定——传什么格式、选哪个模式、怎么验收。 这篇教程把金融学论文降AI教程的常见问题都覆盖了,主要基于嘎嘎降AI(www.aigcleaner.com&#x…...
