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 是一种分层架构,将应用分为以下三个部分:

  1. Model(数据层):负责存储和处理数据。它可以是业务逻辑对象、数据库模型或网络响应对象。

  2. View(视图层):负责显示 UI,并响应用户交互。

  3. ViewModel(视图模型层):负责将 Model 转化为 View 可以使用的数据,同时接收 View 的操作并更新 Model。

        通过 ViewModel,View 和 Model 之间不再直接通信,从而降低了耦合性。

二、MVVM 的主要特点

        数据绑定:通过绑定机制实现 View 和 ViewModel 的双向通信。

        单一职责:Model、View 和 ViewModel 各自专注于自己的职责。

        易于测试:ViewModel 的逻辑独立于 UI,便于单元测试。

三、MVVM 的架构图

View <-----> ViewModel <-----> Model

  1. View:只关心如何展示数据,通常使用 UIKit 或 SwiftUI 构建。
  2. ViewModel:充当中间层,负责从 Model 获取数据,并将其转换为适合展示的数据格式。
  3. 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 开发中,特别是复杂的应用程序中,它能显著提高代码的可维护性和扩展性。通过正确地分层设计,你可以更轻松地应对不断变化的需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/6151.html

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

相关文章

【C++图论 并集查找】2492. 两个城市间路径的最小分数|1679

本文涉及知识点 C图论 并集查找&#xff08;并查集) LeetCode2492. 两个城市间路径的最小分数 给你一个正整数 n &#xff0c;表示总共有 n 个城市&#xff0c;城市从 1 到 n 编号。给你一个二维数组 roads &#xff0c;其中 roads[i] [ai, bi, distancei] 表示城市 ai 和 …

Linux应用编程(五)USB应用开发-libusb库

一、基础知识 1. USB接口是什么&#xff1f; USB接口&#xff08;Universal Serial Bus&#xff09;是一种通用串行总线&#xff0c;广泛使用的接口标准&#xff0c;主要用于连接计算机与外围设备&#xff08;如键盘、鼠标、打印机、存储设备等&#xff09;之间的数据传输和电…

⽤vector数组实现树的存储(孩⼦表示法)c++

在我们遇到的算法题中&#xff0c; ⼀般给出的树结构都是有编号的&#xff0c;这样会简化我们之后存储树的操作 &#xff0c;⼀般提供两个信息&#xff1b; 结点的个数 n;n-1条x结点与y结点相连的边 题⽬描述: ⼀共9个结点셈 1号结点为根节点&#xff0c;接下来8⾏&#xff…

一个基于Python+Appium的手机自动化项目~~

本项目通过PythonAppium实现了抖音手机店铺的自动化询价&#xff0c;可以直接输出excel&#xff0c;并带有详细的LOG输出。 1.excel输出效果: 2. LOG效果: 具体文件内容见GitCode&#xff1a; 项目首页 - douyingoods:一个基于Pythonappium的手机自动化项目&#xff0c;实现了…

基于微信小程序的童装商城的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

方便快捷的软件展示平台查找和下载所需的软件

## 软件展示平台项目概述 背景&#xff1a; 随着互联网的发展&#xff0c;软件的数量日益增长&#xff0c;用户需要一款方便快捷的软件展示平台来查找和下载所需的软件。本软件展示平台旨在为用户提供一个集中展示各类软件的平台&#xff0c;方便用户快速找到所需的软件并进行…

进程、线程和协程的区别

进程、线程和协程的区别 在操作系统中&#xff0c;进程、线程 和 协程 是并发编程中的核心概念。 1. 进程 定义 进程是程序的一次执行过程&#xff0c;是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的地址空间和系统资源。 特点 独立性&#xff1a;每个…

MinIO的安装与使用

目录 1、安装MinIO 1.1 下载 MinIO 可执行文件 1.2 检查 MinIO 是否安装成功 1.3 设置数据存储目录 1.4 配置环境变量&#xff08;可选&#xff09; 1.5 编写启动的脚本 1.6 开放端口 1.7 访问 2、项目实战 2.1 引入依赖 2.2 配置yml文件 2.3 编写Minio配置类 2.4…

CSDN 博客之星 2024:默语的技术进阶与社区耕耘之旅

CSDN 博客之星 2024&#xff1a;默语的技术进阶与社区耕耘之旅 &#x1f31f; 默语&#xff0c;是一位在技术分享与社区建设中坚持深耕的博客作者。今年&#xff0c;我有幸再次入围成为 CSDN 博客之星TOP300 的一员&#xff0c;这既是对过往努力的肯定&#xff0c;也是对未来探…

土壤墒情中土壤 pH 值的监测方法与意义

土壤&#xff0c;作为农作物生长的根基&#xff0c;其质量对农业生产有着深远影响。在衡量土壤质量的众多指标中&#xff0c;土壤 pH 值是极为关键的一项。它不仅反映了土壤的酸碱度&#xff0c;还直接或间接地影响着土壤中养分的有效性、微生物的活性以及农作物的生长发育。因…

Trimble三维激光扫描-地下公共设施维护的新途径【沪敖3D】

三维激光扫描技术生成了复杂隧道网络的高度详细的三维模型 项目背景 纽约州北部的地下通道网络已有100年历史&#xff0c;其中包含供暖系统、电线和其他公用设施&#xff0c;现在已经开始显露出老化迹象。由于安全原因&#xff0c;第三方的进入受到限制&#xff0c;在没有现成纸…

开发环境搭建-1:配置 WSL (类 centos 的 oracle linux 官方镜像)

一些 Linux 基本概念 个人理解&#xff0c;并且为了便于理解&#xff0c;可能会存在一些问题&#xff0c;如果有根本上的错误希望大家及时指出 发行版 WSL 的系统是基于特定发行版的特定版本的 Linux 发行版 有固定组织维护的、开箱就能用的 Linux 发行版由固定的团队、社…

从零到上线:Node.js 项目的完整部署流程(包含 Docker 和 CICD)

从零到上线&#xff1a;Node.js 项目的完整部署流程&#xff08;包含 Docker 和 CI/CD&#xff09; 目录 项目初始化&#xff1a;构建一个简单的 Node.js 应用设置 Docker 环境&#xff1a;容器化你的应用配置 CI/CD&#xff1a;自动化构建与部署上线前的最后检查&#xff1a;…

安卓动态设置Unity图形API

命令行方式 Unity图像api设置为自动,安卓动态设置Vulkan、OpenGLES Unity设置 安卓设置 创建自定义活动并将其设置为应用程序入口点。 在自定义活动中,覆盖字符串UnityPlayerActivity。updateunitycommandlineararguments (String cmdLine)方法。 在该方法中,将cmdLine…

python如何导出数据到excel文件

python导出数据到excel文件的方法&#xff1a; 1、调用Workbook()对象中的add_sheet()方法 wb xlwt.Workbook() ws wb.add_sheet(A Test Sheet) 2、通过add_sheet()方法中的write()函数将数据写入到excel中&#xff0c;然后使用save()函数保存excel文件 ws.write(0, 0, 1234…

虚幻基础-1:cpu挑选(14600kf)

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录 ue非常吃cpu拉满主频打开项目编写蓝图运行原因 时间长 关于压力测试 本文以14600kf为例&#xff0c;双12购入&#xff0c;7月份产。 ue非常吃cpu 经本人测试&#xff0c;ue是非常吃cpu的。 拉满主频 无论任何时间…

MECD+: 视频推理中事件级因果图推理--VLM长视频因果推理

论文链接&#xff1a;https://arxiv.org/pdf/2501.07227v1 1. 摘要及主要贡献点 摘要&#xff1a; 视频因果推理旨在从因果角度对视频内容进行高层次的理解。然而&#xff0c;目前的研究存在局限性&#xff0c;主要表现为以问答范式执行&#xff0c;关注包含孤立事件和基本因…

mapbox加载geojson,鼠标移入改变颜色,设置样式以及vue中的使用

全国地图json数据下载地址 目录 html加载全部代码 方式一&#xff1a;使用html方式加载geojson 1. 初始化地图 2. 加载geojson数据 设置geojson图层样式&#xff0c;设置type加载数据类型 设置线条 鼠标移入改变颜色&#xff0c;设置图层属性&#xff0c;此处是fill-extru…

接上篇基于Alertmanager 配置钉钉告警

Alertmanager 是一个用于处理和管理 Prometheus 警报的开源工具。它负责接收来自 Prometheus 服务器的警报&#xff0c;进行去重、分组、静默、抑制等操作&#xff0c;并通过电子邮件、PagerDuty、Slack 等多种渠道发送通知。 主要功能 去重&#xff1a;合并相同或相似的警报&a…

通过视觉语言模型蒸馏进行 3D 形状零件分割

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01;对应英文要求比较高&#xff0c;特此说明&#xff01; Abstract This paper proposes a cross-modal distillation framework, PartDistill, which transfers 2D knowledge from vision-language models …