当前位置: 首页 > news >正文

一场陟遐自迩的 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 性能小“瓶颈”的解决思路,并随后循序渐进的将其优化于无形。

感谢观赏,再会啦!😎

http://www.xdnf.cn/news/357301.html

相关文章:

  • 介质访问控制(MAC)
  • sqli-labs靶场18-22关(http头)
  • 数据分析与逻辑思维:六步解决业务难题;参考书籍《数据分析原理:6步解决业务分析难题 (周文全, 黄怡媛, 马炯雄)》
  • C# WinForm 如何高效地将大量数据从 CSV 文件导入 DataGridView
  • JavaScript 基础
  • 成龙电影中的三菱汽车
  • 退货处理费归零:亚马逊卖家年度成本节省路径解析
  • 硅基计划 学习总结 拾贰
  • WebSocket与Socket.IO实现简易客服聊天系统全解析
  • Spring Web MVC快速入门
  • [css]纯css绘制三角形
  • MindSpore框架学习项目-ResNet药物分类-数据增强
  • HTML应用指南:利用POST请求获取全国德邦快递服务网点位置信息
  • C++中extern关键字详解:不同情况下的使用方式
  • Text Based Person Search 研究进展汇报:主要问题、数据集、未来方向
  • ATH12K驱动框架架构图
  • vue3 全局注册自定义指令,input聚焦失焦展示对应值
  • Java LocalDateTime类详解:高效处理日期时间
  • 面试题:Java集合框架高频面试题总结
  • 何时需要import css文件?怎么知道需要导入哪些css文件?为什么webpack不提示CSS导入?(导入css导入规则、css导入规范)
  • Nginx修改日志时间格式
  • WHAT - 用户访问产品个性化延迟问题和技术手段
  • 从投入产出、效率、上手难易度等角度综合对比 pytest 和 unittest 框架
  • 第三节:条件语句与循环:控制程序流程
  • 特殊配合力(SCA)作为全基因组关联分析(GWAS)的表型,其生物学意义和应用价值
  • MQTT:轻量级物联网通信协议详解
  • Vulnhub Lazysysadmin靶机攻击实战(一)
  • logback日志输出到项目运行目录
  • Git_idea界面进行分支合并到主分支详细操作
  • Elasticsearch内存管理与JVM优化:原理剖析与最佳实践