概述
自从 SwiftUI 诞生那天起,我们秃头码农们就仿佛打开了一个全新的撸码世界,再辅以 CoreData 框架的鼎力相助,打造一款持久存储支持的 App 就像探囊取物般的 Easy。
话虽如此,不过 CoreData 虽好,稍不留神也可能会让代码执行速度“蜗行牛步”,这该如何解决呢?
在本篇博文中,您将学到如下内容:
这是两篇偏向撸码的博文,里面有较多的源代码展示,我们会循序渐进地完成整个优化目标,希望大家能够喜欢。
那还等什么呢?让我们马上开始 CoreData 优化大冒险吧!
Let’s go!!!😉
1. 当前源代码执行的“小瓶颈”
要想小试拳脚,我们必须先有需要优化的代码。下面我就满足大家吧:
struct MonthCountsView: View {
@Environment(\.managedObjectContext) var context
let counter: ProjectCounter
let year: Int
let month: Int
@State private var isShowMonthCountsChart = false
var body: some View {
// #1
let daysCounts = try! counter.queryDaysCounts(year: year, month: month, context: context)
// #2
let 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 in
let dayCounts = daysCounts[day]!
let totalCount = dayCounts.totalCount
NavigationLink {
List {
LabeledContent("当日总计数") {
Text("\(totalCount)\(counter.unit ?? "")")
.font(.title2)
.foregroundStyle(counter.nature.data.color)
}
Section("单次计数") {
if let counts = dayCounts.counts {
ForEach(counts, id: \.time) { trace in
HStack {
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: Int
var totalCount: Int = 0
var monthlyAvg: Float = 0.0
var 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: Int
let month: Int
var totalCount: Int = 0
var daylyAvg: Float = 0.0
var 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: Date
var totalCount: Int = 0
var counts: [(time: Date, count: Int)]?
}
如您所见,上面 MonthCountsView 视图的问题在于:每次重新渲染(Rendering)它的 body 内容时,我们都会重新计算 daysCounts 字典的内容(而且这是在主线程中完成的),这无疑有些庸人自扰。
那么,我们该如何进一步优化它的执行效率呢?这看似有些一筹莫展。
别急,在下一篇博文中,我们将会一步步“聚沙成塔”,最终完成整个优化目标,期待吧!
想要进一步系统地学习 Swift 开发的小伙伴们,可以来我的《Swift 语言开发精讲》专栏逛一逛哦:
总结
在本篇博文中,我们介绍了 SwiftUI + CoreData 代码在执行时遇到的一个效率瓶颈,并给出了问题相关的详细源代码。
感谢观赏,我们下一篇再见吧!😎