一场陟遐自迩的 SwiftUI + CoreData 性能优化之旅(上)

在这里插入图片描述

概述

自从 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 代码在执行时遇到的一个效率瓶颈,并给出了问题相关的详细源代码。

感谢观赏,我们下一篇再见吧!😎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大熊猫侯佩

赏点钱让我买杯可乐好吗 ;)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值