一场陟遐自迩的 SwiftUI + CoreData 性能优化之旅(下)
概述
自从 SwiftUI 诞生那天起,我们秃头码农们就仿佛打开了一个全新的撸码世界,再辅以 CoreData 框架的鼎力相助,打造一款持久存储支持的 App 就像探囊取物般的 Easy。
话虽如此,不过 CoreData 虽好,稍不留神也可能会让代码执行速度“蜗行牛步”,这该如何解决呢?
在本篇博文中,您将学到如下内容:
- 概述
- 2. 先谈优化思路
- 3. 循序渐进与大刀阔斧
- 4. 打完收工
- 总结
这是两篇偏向撸码的博文,里面有较多的源代码展示,我们会循序渐进地完成整个优化目标,希望大家能够喜欢。
那还等什么呢?让我们马上开始 CoreData 优化大冒险吧!
Let’s go!!!😉
2. 先谈优化思路
为了能够进一步从整体上鸟瞰全局,是时候将 MonthCountsView 父视图的源代码呈现给大家了:
struct CounterView: View {@Environment(\.managedObjectContext) var contextlet counter: ProjectCounter@State private var yearsCountsData = [ProjectCounter.YearCountsData]()LazyVStack {ForEach(yearsCountsData) { yearData inVStack {HStack {Text(verbatim: "\(yearData.year)年").font(.title.weight(.heavy))Spacer()Text("年总计数:\(yearData.totalCount)\(counter.unit ?? "")").fontWeight(.bold).foregroundStyle(counter.nature.data.color)}if let monthsCounts = yearData.monthsCountSortedAry {ForEach(monthsCounts) { monthData inDisclosureGroup {MonthCountsView(yearsCountsData: $yearsCountsData, counter: counter, year: monthData.year, month: monthData.month)} label: {HStack {Text("\(monthData.month)月")Spacer()Text("\(monthData.totalCount)\(counter.unit ?? "")")}}}}}}}.task {// 计算年计数数据yearsCountsData = counter.calcYearsCountsData()}
}
回顾一下之前 MonthCountsData 结构的实现,其中有一个 daysCounts: [Int: DayCountsData]? 可选类型,它在默认情况下并不会被主动填充,我们为什么不把它利用起来呢?
我们的思路是:在 MonthCountsView 首次显示时计算该月的月计数 [Int: DayCountsData] 字典数据,并将其写回到父视图 yearsCountsData 对应的月计数对象中去,这样下次相同 MonthCountsView 视图再次加入渲染树时,我们即可直接使用这个字典数据了。
而且,我们希望月计数字典数据能够在后台线程里完成,这样可以进一步提高主线程的“丝滑”程度。因为其计算方法 queryDaysCounts() 已经在设计时就支持传入一个“可爱”的托管上下文对象,这无疑让我们后续的优化操作“易如拾芥”:
func queryDaysCounts(year: Int, month: Int, context: NSManagedObjectContext) throws -> [Int: DayCountsData] {// 实现从略...
}
在将 CoreData 的托管对象从后台线程传入主线程时,要特别小心,否则可能会成为“池鱼林木”。更多与此相关的介绍,请小伙伴们移步如下链接观赏精彩的内容:
- 消失的它:揭开 CoreData 托管对象神秘的消失之谜(上)
- 消失的它:揭开 CoreData 托管对象神秘的消失之谜(下)
3. 循序渐进与大刀阔斧
当思路已经成型,当脱发已成往事,我们就可以起身向最终的目标前进了。在旅途中,我们要心细且胆大。这有点儿像开车:该慢的时候一定要慢,而该快的时候你也要把速度提起来。
首先,我们在 MonthCountsView 视图中新增一个年计数绑定,用来绑定父视图中的对应数据:
/// 所有年计数记录的绑定,便于将计算结果写回,避免反复计算月计数数据
@Binding var yearsCountsData: [ProjectCounter.YearCountsData]
接着,我们直接删除之前 MonthCountsView 视图里 #1 处的变量定义,并增加新的 daysCounts 同名属性:
@State private var daysCounts = [Int: ProjectCounter.DayCountsData]()
最后,我们让 MonthCountsView 视图在显示时按需计算相关的月计数数据:
.task {let yearIndex = yearsCountsData.firstIndex { $0.year == year}!if let monthData = yearsCountsData[yearIndex].monthsCounts?[month], let daysCounts = monthData.daysCounts {self.daysCounts = daysCounts} else {let container = Model.shared.controller.containercontainer.performBackgroundTask { bgContext inlet daysCounts = try! counter.queryDaysCounts(year: year, month: month, context: bgContext)DispatchQueue.main.async {self.daysCounts = daysCounts// 将计算结果作为缓存,写回到父视图的年计数中去yearsCountsData[yearIndex].monthsCounts?[month]?.daysCounts = daysCounts}}}
}
在上面的代码里,我们主要做了这样几件事:
- 找到当前月对应年的计数数据 YearCountsData;
- 如果年计数数据对应的月数据已经缓存,我们直接使用它;
- 否则,我们在后台计算月计数数据,并在计算完毕后回到主线程写入年计数数据的缓存中;
这样一来,我们的月计数数据只需在 MonthCountsView 视图首次显示时计算一次,之后即可享用缓存中现成的数据了。
4. 打完收工
回到 MonthCountsView 的父视图 CounterView 中,我们修改一下 MonthCountsView 的调用签名:
if let monthsCounts = yearData.monthsCountSortedAry {ForEach(monthsCounts) { monthData inDisclosureGroup {MonthCountsView(yearsCountsData: $yearsCountsData, counter: counter, year: monthData.year, month: monthData.month)} label: {HStack {Text("\(monthData.month)月")Spacer()Text("\(monthData.totalCount)\(counter.unit ?? "")")}}}
}
现在,一切都已准备就绪,我们再回到 Xcode 预览中一窥究竟新代码的表现吧:
值得注意的是,除了 Grid 布局可以从 MonthCountsView 视图的 daysCounts 缓存受益以外,其中的月计数图表(Chart)同样也可以得到妥妥地加速,正所谓一石二鸟、一箭双雕也,棒棒哒!💯
想要进一步系统地学习 Swift 开发的小伙伴们,可以来我的《Swift 语言开发精讲》专栏逛一逛哦:
- 《Swift 语言开发精讲》
总结
在本篇博文中,我们讨论了一个 SwiftUI + CoreData 性能小“瓶颈”的解决思路,并随后循序渐进的将其优化于无形。
感谢观赏,再会啦!😎
相关文章:

一场陟遐自迩的 SwiftUI + CoreData 性能优化之旅(下)
概述 自从 SwiftUI 诞生那天起,我们秃头码农们就仿佛打开了一个全新的撸码世界,再辅以 CoreData 框架的鼎力相助,打造一款持久存储支持的 App 就像探囊取物般的 Easy。 话虽如此,不过 CoreData 虽好,稍不留神也可能会…...

数字人驱动/动画方向最新顶会期刊论文收集整理 | AAAI 2025
会议官方论文列表:https://ojs.aaai.org/index.php/AAAI/issue/view/624 以下论文部分会开源代码,若开源,会在论文原文的摘要下方给出链接。 语音驱动头部动画/其他 EchoMimic: Lifelike Audio-Driven Portrait Animations through Editabl…...
Java+Selenium+快代理实现高效爬虫
目录 一、前言二、Selenium简介三、环境准备四、代码实现4.1 创建WebDriver工厂类4.2 创建爬虫主类4.3 配置代理的注意事项 六、总结与展望 一、前言 在Web爬虫技术中,Selenium作为一款强大的浏览器自动化工具,能够模拟真实用户操作,有效应对…...

数据结构 集合类与复杂度
文章目录 📕1. 集合类📕2. 时间复杂度✏️2.1 时间复杂度✏️2.2 大O渐进表示法✏️2.3 常见的时间复杂度量级✏️2.4 常见时间复杂度计算举例 📕3. 空间复杂度 📕1. 集合类 Java 集合框架(Java Collection Framework…...

Python学习笔记--Django的安装和简单使用(一)
一.简介 Django 是一个用于构建 Web 应用程序的高级 Python Web 框架。Django 提供了一套强大的工具和约定,使得开发者能够快速构建功能齐全且易于维护的网站。Django 遵守 BSD 版权,初次发布于 2005 年 7 月, 并于 2008 年 9 月发布了第一个正式版本 1…...

SecureCRT网络穿透/代理
场景 公司的办公VPN软件只有Windows系统版本,没有Macos系统版本,而日常开发过程中需要先登录VPN后,然后才能登录应用服务器。 目的:Macos系统在使用SecureCRT时,登录服务器,需要走Parallels Desktop进行网络…...

视频添加字幕脚本分享
脚本简介 这是一个给视频添加字幕的脚本,可以方便的在指定的位置给视频添加不同大小、字体、颜色的文本字幕,添加方式可以直接修改脚本中的文本信息,或者可以提前编辑好.srt字幕文件。脚本执行环境:windowsmingwffmpeg。本方法仅…...

OrangePi Zero 3学习笔记(Android篇)4 - eudev编译(获取libudev.so)
目录 1. Ubuntu中编译 2. NDK环境配置 3. 编译 4. 安装 这部分主要是为了得到libudev(因为原来的libudev已经不更新了),eudev的下载地址如下: https://github.com/gentoo/eudev 相应的代码最好是在Ubuntu中先编译通过&#…...
JavaSE核心知识点02面向对象编程02-04(包和导入)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 JavaSE核心知识点02面向对象编程02-04&#…...
【Git】查看tag
文章目录 1. 查看当前提交是否有tag2. 查看最近的tag3. 查看所有tag 有时候需要基于某个tag拉分支,记录下怎么查看tag。 1. 查看当前提交是否有tag git tag --points-at HEAD该命令可直接检查当前提交(HEAD)是否关联了任何tag。 若当前提交…...

华为昇腾910B通过vllm部署InternVL3-8B教程
前言 本文主要借鉴:VLLM部署deepseek,结合自身进行整理 下载模型 from modelscope import snapshot_download model_dir snapshot_download(OpenGVLab/InternVL3-8B, local_dir"xxx/OpenGVLab/InternVL2_5-1B")环境配置 auto-dl上选择单卡…...

upload-labs靶场通关详解:第三关
一、分析源代码 代码注释如下: <?php // 初始化上传状态和消息变量 $is_upload false; $msg null;// 检查是否通过POST方式提交了表单 if (isset($_POST[submit])) {// 检查上传目录是否存在if (file_exists(UPLOAD_PATH)) {// 定义禁止上传的文件扩展名列表…...

星光云720全景VR系统升级版,720全景,360全景,vr全景,720vr全景
星光云720全景VR系统升级版,720全景,360全景,vr全景,720vr全景 星光云全景系统 系统体验地址 https://720.ailemon.cc 星光云全景新版体验地址 全景系统功能简介 基础设置:作品信息,加载样式ÿ…...

第十六节:图像形态学操作-顶帽与黑帽变换
一、引言:形态学操作的视觉魔法 在数字图像处理领域,形态学操作犹如一柄精巧的解剖刀,能够精准地提取图像特征、消除噪声干扰,并增强关键细节。OpenCV作为计算机视觉的瑞士军刀,提供了一套完整的形态学处理工具。在掌…...

将 iconfont 图标转换成element-plus也能使用的图标组件
在做项目时发现,element-plus的图标组件,不能像文档示例中那样使用 iconfont 的图标。经过研究发现,element-plus的图标封装成了vue组件,组件内容是一个svg,然后以组件的方式引入和调用图标。根据这个思路,…...

大模型系列(四)--- GPT2: Language Models are Unsupervised Multitask Learners
论文链接: Language Models are Unsupervised Multitask Learners 点评: GPT-2采用了与GPT-1类似的架构,将参数规模增加到了15亿,并使用大规模的网页数据集WebText 进行训练。正如GPT-2 的论文所述,它旨在通过无监督语…...
等保系列(三):等保测评的那些事
一、等保测评主要做什么 1、测评准备阶段 (1)确定测评对象与范围 明确被测系统的边界、功能模块、网络架构及承载的业务。 确认系统的安全保护等级(如二级、三级)。 (2)签订测评合同 选择具备资质的测…...

ABP vNext + EF Core 实战性能调优指南
ABP vNext EF Core 实战性能调优指南 🚀 目标 本文面向中大型 ABP vNext 项目,围绕查询性能、事务隔离、批量操作、缓存与诊断,系统性地给出优化策略和最佳实践,帮助读者快速定位性能瓶颈并落地改进。 📑 目录 ABP vN…...

高品质办公楼成都国际数字影像产业园核心业务
成都国际数字影像产业园的核心业务,围绕构建专业化的数字影像文创产业生态系统展开,旨在打造高品质、高效率的产业发展平台。 产业集群构建与生态运营 园区核心业务聚焦于吸引和培育数字影像及相关文创领域的企业,形成产业集聚效应。具体包…...

MindSpore框架学习项目-ResNet药物分类-构建模型
目录 2.构建模型 2.1定义模型类 2.1.1 基础块ResidualBlockBase ResidualBlockBase代码解析 2.1.2 瓶颈块ResidualBlock ResidualBlock代码解释 2.1.3 构建层 构建层代码说明 2.1.4 定义不同组合(block,layer_nums)的ResNet网络实现 ResNet组建类代码解析…...

【Spring Boot】Spring Boot + Thymeleaf搭建mvc项目
Spring Boot Thymeleaf搭建mvc项目 1. 创建Spring Boot项目2. 配置pom.xml3. 配置Thymeleaf4. 创建Controller5. 创建Thymeleaf页面6. 创建Main启动类7. 运行项目8. 测试结果扩展:添加静态资源 1. 创建Spring Boot项目 打开IntelliJ IDEA → New Project → 选择M…...
线程中常用的方法
知识点详细说明 Java线程的核心方法集中在Thread类和Object类中,以下是新增整合后的常用方法分类解析: 1. 线程生命周期控制 方法作用注意事项start()启动新线程,JVM调用run()方法多次调用会抛出IllegalThreadStateException(线程状态不可逆)。run()线程的任务逻辑直接调…...

学习spring boot-拦截器Interceptor,过滤器Filter
目录 拦截器Interceptor 过滤器Filter 关于过滤器的前置知识可以参考: 过滤器在springboot项目的应用 一,使用WebfilterServletComponentScan 注解 1 创建过滤器类实现Filter接口 2 在启动类中添加 ServletComponentScan 注解 二,创建…...
为啥大模型一般将kv进行缓存,而q不需要
1. 自回归生成的特点 大模型(如 GPT 等)在推理时通常采用自回归生成的方式: 模型逐个生成 token,每次生成一个新 token 时,需要重新计算注意力。在生成第 t 个 token 时,模型需要基于前 t-1 个已生成的 t…...

雷赛伺服L7-EC
1电子齿轮比: 电机圈脉冲1万 (pa11的值 x 4倍频) 2电机刚性: pa003 或者 0x2003 // 立即生效的 3LED显示: PA5.28 1 电机速度 4精度: PA14 //默认30,超过3圈er18…...

阅文集团C++面试题及参考答案
能否不使用锁保证多线程安全? 在多线程编程中,锁(如互斥锁、信号量)是实现线程同步的传统方式,但并非唯一方式。不使用锁保证多线程安全的核心思路是避免共享状态、使用原子操作或采用线程本地存储。以下从几个方面详…...

AVL树:保持平衡的高效二叉搜索树
目录 一、AVL树的概念 1. 二叉搜索树的局限性 2. AVL树的定义 二、AVL树节点结构 三、AVL树的插入操作 1. 插入流程 2. 代码实现片段 四、AVL树的旋转调整 1. 左单旋(RR型) 2. 右单旋(LL型) 3. 左右双旋(LR型…...
打造专属AI好友:小智AI聊天机器人详解
打造专属AI好友:小智AI聊天机器人详解 在当下的科技热潮中,AI正迅速改变着我们的生活,成为了科技领域的新宠。而今,借助开源项目的力量,你可以亲手打造一个智能小助手——小智AI聊天机器人。它不仅是一个技术探索的窗…...

Webpack基本用法学习总结
Webpack 基本使用核心概念处理样式资源步骤: 处理图片资源修改图片输出文件目录 自动清空上次打包的内容EslintBabel处理HTML资源搭建开发服务器生产模式提取css文件为单独文件问题: Css压缩HTML压缩 小结1高级SourceMap开发模式生产模式 HMROneOfInclud…...

阿里云服务器数据库故障排查指南?
阿里云服务器数据库故障排查指南? 以下是针对阿里云服务器(如ECS自建数据库或阿里云RDS等托管数据库)的故障排查指南,涵盖常见问题的定位与解决方案: 一、数据库连接失败 检查网络连通性 ECS自建数据库 确认安全组规则放行数据库…...