概述
自从 SwiftUI 诞生那天起,我们秃头码农们就仿佛打开了一个全新的撸码世界,再辅以 CoreData 框架的鼎力相助,打造一款持久存储支持的 App 就像探囊取物般的 Easy。
话虽如此,不过 CoreData 虽好,稍不留神也可能会让代码执行速度“蜗行牛步”,这该如何解决呢?
在本篇博文中,您将学到如下内容:
- 概述
- 1. 当前源代码执行的“小瓶颈”
- 总结
这是两篇偏向撸码的博文,里面有较多的源代码展示,我们会循序渐进地完成整个优化目标,希望大家能够喜欢。
那还等什么呢?让我们马上开始 CoreData 优化大冒险吧!
Let’s go!!!😉
1. 当前源代码执行的“小瓶颈”
要想小试拳脚,我们必须先有需要优化的代码。下面我就满足大家吧:
struct MonthCountsView: View {@Environment(\.managedObjectContext) var contextlet counter: ProjectCounterlet year: Intlet month: Int@State private var isShowMonthCountsChart = falsevar body: some View {// #1let daysCounts = try! counter.queryDaysCounts(year: year, month: month, context: context)// #2let days = daysCounts.keys.sorted(using: SortDescriptor(\.self, order: .reverse))VStack {Button(isShowMonthCountsChart ? "月计数" : "月计数图表") {withAnimation(.bouncy) {isShowMonthCountsChart.toggle()}}.frame(maxWidth: .infinity, alignment: .leading)if isShowMonthCountsChart {MonthCountsChart(counter: counter, daysCountsDict: daysCounts).frame(minHeight: 200)} else {LazyVGrid(columns: [GridItem](repeating: .init(.adaptive(minimum: 100, maximum: 120), spacing: 8), count: 4)) {ForEach(days, id: \.self) { day inlet dayCounts = daysCounts[day]!let totalCount = dayCounts.totalCountNavigationLink {List {LabeledContent("当日总计数") {Text("\(totalCount)\(counter.unit ?? "")").font(.title2).foregroundStyle(counter.nature.data.color)}Section("单次计数") {if let counts = dayCounts.counts {ForEach(counts, id: \.time) { trace inHStack {Text(Common.timeHHmmFt.string(from: trace.time)).monospacedDigit()Spacer()Text("\(trace.count)\(counter.unit ?? "")").font(.title3).foregroundStyle(counter.nature.data.color)}}}}}.listStyle(.plain).navigationTitle(Common.onlyDateFt.string(from: dayCounts.date))} label: {VStack {HStack {Text("\(Common.tinyDateFt.string(from: dayCounts.date))日").font(.headline).frame(maxWidth: .infinity, alignment: .leading)}.padding(.leading).frame(minHeight: 50)Text("\(totalCount)\(Text("\(counter.unit ?? "")").font(.subheadline))").font(.title).padding(.bottom)}.foregroundStyle(.white).monospacedDigit().background(counter.nature.data.color.gradient.opacity(0.88), in: RoundedRectangle(cornerRadius: 10))}}}}}}
}
上面的代码虽然有点冗长,但本质上却很简单。我们主要做了以下几件事:
- 使用 isShowMonthCountsChart 状态切换月计数图表和 Grid 显示;
- 在 #1 代码处,我们调用计数器的 queryDaysCounts 方法,来获取指定月的计数记录,返回的结果是一个 [Int: DayCountsData] 字典;
- 在 #2 代码处,我们将上述字典所有键反向排序并生成所有日的数组,这会将最近的日排在最前面;
为了进一步便于还未秃小码农们的理解,我们下面将缺失的、与计数相关的数据结构一并贴出来:
struct YearCountsData: Identifiable {var id: String {"\(year)"}let year: Intvar totalCount: Int = 0var monthlyAvg: Float = 0.0var monthsCounts: [Int: MonthCountsData]?var monthsCountSortedAry: [MonthCountsData]? {if let data = monthsCounts {let sortedKeys = data.keys.sorted(using: SortDescriptor(\.self, order: .reverse))return sortedKeys.map { data[$0]! }}return nil}}struct MonthCountsData: Identifiable {var id: String {"\(year).\(month)"}let year: Intlet month: Intvar totalCount: Int = 0var daylyAvg: Float = 0.0var daysCounts: [Int: DayCountsData]?var daysCountSortedAry: [DayCountsData]? {if let data = daysCounts {let sortedKeys = data.keys.sorted(using: SortDescriptor(\.self, order: .reverse))return sortedKeys.map { data[$0]! }}return nil}}struct DayCountsData: Identifiable {var id: Date {date}let date: Datevar totalCount: Int = 0var counts: [(time: Date, count: Int)]?}
如您所见,上面 MonthCountsView 视图的问题在于:每次重新渲染(Rendering)它的 body 内容时,我们都会重新计算 daysCounts 字典的内容(而且这是在主线程中完成的),这无疑有些庸人自扰。
那么,我们该如何进一步优化它的执行效率呢?这看似有些一筹莫展。
别急,在下一篇博文中,我们将会一步步“聚沙成塔”,最终完成整个优化目标,期待吧!
想要进一步系统地学习 Swift 开发的小伙伴们,可以来我的《Swift 语言开发精讲》专栏逛一逛哦:
- 《Swift 语言开发精讲》
总结
在本篇博文中,我们介绍了 SwiftUI + CoreData 代码在执行时遇到的一个效率瓶颈,并给出了问题相关的详细源代码。
感谢观赏,我们下一篇再见吧!😎