概覽
在 CoreData 支持的 App 中,一種常見操作就是計算數據庫表中指定字段的最大值(或最小值)。就是這樣一種看起來“不足掛齒”的任務,可能稍不留神就會“馬失前蹄”。
在實際的代碼中,我們怎樣才能既迅速又簡潔的找出字段的最大值呢?
在本篇博文中,您將學到如下內容:
- 概覽
- 0. CoreData 表結構
- 1. 借助現成的關系屬性(Relation Property)
- 2. 使用“遠古” NSArray 的力量
- 3. 使用 CoreData Fetch 請求
- 總結
相信學完本課后,大家 CoreData 的算法武器庫中又會多幾種“削鐵如泥”的利刃啦。
本文中所有代碼的測試環境為:
- MBA 2022,M2,內存 16 GB
- macOS 15.3.2(Sequoia)
那還等什么呢?Let‘s find out!!!😉
0. CoreData 表結構
我們即將展示的所有算法都將在如下 CoreData 表結構中“施展拳腳”:
- Project 表包含多個 VictoryStage 對象,這通過 Project 的 records 關系體現出來;
- VictoryStage 表中包含一個類型為 Date? 的 end 字段,它用來表示 VictoryStage 的結束日期;
數據庫中被測試的特定 Project 對象里大約包含不到 400 個 VictoryStage 對象,我們要計算的就是:這些對象中 end 字段最大的值
為了方便,我們首先創建一個將集合(Set)類型 records 轉換為數組(Array)的輔助方法:
extension Project {var vstages: [VictoryStage] {if let recordAry = records?.allObjects as? [VictoryStage] {return recordAry}return []}
}
接下來,小伙伴們就可以“馳馬試劍”,恣意測試我們的算法啦!
1. 借助現成的關系屬性(Relation Property)
既然 Project 包含一對多的 vstages 關系,我們最簡單的方法是直接從此入手,找到 end 值最大(時間上最新)的那一個 VictoryStage 對象。
因為最新的 Swift Testing 框架還缺少內置的測速機制,我們只能暫時退回到 XCTest 的 measure 方法中去。這里需要提出批評,希望蘋果趕快在 Swift Testing 2 中將其補上哦。
關于更多馴服 Swift Testing 測試的小妙招,請小伙伴們移步如下鏈接觀賞精彩的內容:
- 用接地氣的例子趣談 WWDC 24 全新的 Swift Testing 入門(一)
- 用接地氣的例子趣談 WWDC 24 全新的 Swift Testing 入門(二)
- 用接地氣的例子趣談 WWDC 24 全新的 Swift Testing 入門(三)
- WWDC24(Xcode 16)中全新的 Swift Testing 使用進階
- Xcode 16 中 Swift Testing 的參數化(Parameterized)機制趣談
算法的實現很簡單:我們只需將 vstages 數組按照 end 從新到舊排序,結果數組中第一個 VictoryStage 對象就是我們想要的。
func testPerformanceWithRelationProperty() throws {// 利用 Project 關系屬性來計算最新的 VictoryStagemeasure {let vstages = project.vstageslet mostRecent = vstages.sorted(using: SortDescriptor(\VictoryStage.end, order: .reverse)).firstprint("最新的 VStage: \(mostRecent?.end ?? Date.distantPast)")}
}
從單元測試的結果來看,10 次運行的平均耗時為:0.00464 秒。
2. 使用“遠古” NSArray 的力量
我們知道,Swift 中的數組(Array)都是由底層的 Objc 數據結構 NSArray 在默默“撐腰”的。而 NSArray 又自帶了一個 sortedArray 排序方法:
So,我們可以很快據此寫出 NSArray 的排序算法:
func testPerformanceWithRelationPropertyNSArray() throws {// 利用 Project 關系屬性的 NSArray 來計算最新的 VictoryStagemeasure {let vstages = project.vstageslet mostRecent = ((vstages as NSArray).sortedArray(using: [.init(keyPath: \VictoryStage.end, ascending: false)]) as! [VictoryStage]).firstprint("最新的 VStage: \(mostRecent?.end ?? Date.distantPast)")}
}
美中不足的是,這種方法需要做 2 次類型轉換。
小伙伴們可能覺得 NSArray 排序算法和前一種速度差不了多少,不過實際結果可能會讓你們大吃一驚:
是的,你們沒有看錯!NSArray 排序比 Array 排序快了一個數量級,只需 0.000559 秒。
3. 使用 CoreData Fetch 請求
為了能夠充分發揮 CoreData 底層里 Sqlite 原生查詢的“奧義”,我們可以直接利用 Fetch 請求來排憂解難:
func testPerformanceWithFetchPredicate() throws {// 利用 CoreData Fetch 請求來計算最新的 VictoryStagemeasure {let req = VictoryStage.fetchRequest()req.predicate = .init(format: "project = %@", project)req.sortDescriptors = [.init(keyPath: \VictoryStage.end, ascending: false)]req.fetchLimit = 1let mostRecent = try! context.fetch(req).firstprint("最新的 VStage: \(mostRecent?.end ?? Date.distantPast)")}
}
在上面的代碼中,我們做了這樣幾件事:
- 創建一個 Fetch Request;
- 穩妥的設置了它的 Predicate 和排序屬性;
- 將其搜索結果的數量限制為 1 ;
運行看一下效果!恭喜大家,我們又成功的將性能提升了幾倍,現在只需 0.000196 秒了。
在下一篇文章中,我們將再接再厲,使用 NSExpression 表達式方法來為本系列博文畫上一個完美的句號。
想要進一步系統地學習 Swift 開發的小伙伴們,可以來我的《Swift 語言開發精講》專欄逛一逛哦:
- 《Swift 語言開發精講》
總結
在本篇博文中,我們討論了 CoreData 中計算字段最大值的三種方法,任君選用。
感謝觀賞,我們下一篇再見!😎