“群芳爭艷”:CoreData 4 種方法計算最大值的效率比較(下)

概覽

在 CoreData 支持的 App 中,一種常見操作就是計算數(shù)據(jù)庫表中指定字段的最大值(或最小值)。就是這樣一種看起來“不足掛齒”的任務,可能稍不留神就會“馬失前蹄”。

在實際的代碼中,我們怎樣才能既迅速又簡潔的找出字段的最大值呢?

在本篇博文中,您將學到如下內(nèi)容:

  1. 使用 NSExpression 表達式
  2. 孰是孰非?

相信學完本課后,大家 CoreData 的算法武器庫中又會多幾種“削鐵如泥”的利刃啦。

本文中所有代碼的測試環(huán)境為:

  • MBA 2022,M2,內(nèi)存 16 GB
  • macOS 15.3.2(Sequoia)

那還等什么呢?Let‘s find out?。?!;)


4. 使用 NSExpression 表達式

除了按時間排序 VictoryStage 數(shù)組以外,我們還可以直接利用 NSExpression 表達式自帶的 max 函數(shù)來計算 VictoryStage 托管對象最新的 end 值。

大多數(shù)小伙伴們可能都對 NSExpression 的使用比較陌生,其實利用 NSExpression 我們可以寫出極具靈活性的比較邏輯表達式:

func testPerformanceWithFetchExpression() throws {
    // 利用 NSExpression 的 max 函數(shù)來計算最新的 VictoryStage
    measure {
        let req = NSFetchRequest<NSDictionary>(entityName: "VictoryStage")
        req.predicate = .init(format: "project = %@", project)
        let maxExpr = NSExpression(forKeyPath: \VictoryStage.end)
        let maxExprDesc = NSExpressionDescription()
        maxExprDesc.name = "endDay"
        maxExprDesc.expression = NSExpression(forFunction: "max:", arguments: [maxExpr])
        maxExprDesc.expressionResultType = .dateAttributeType
        req.propertiesToFetch = [maxExprDesc]
        req.resultType = .dictionaryResultType

        let result = try! context.fetch(req).first as! [String: Any]
        let endDay = result["endDay"] as! Date
        print("最新 VStage 的日期: \(endDay)")
    }
}

在上面的實現(xiàn)中,我們完成的主要工作是:

  1. 創(chuàng)建一個計算 VictoryStage.end 字段最大值(max:)的 NSExpression 表達式;
  2. 將該表達式應用在 Fetch Request 上;
  3. 調用 CoreData 上下文的 fetch 方法來求得表達式的結果;
  4. 從結果字典中解析最近的日期;

在運行結果中,我們可以看到實際耗時為 0.000389 秒,在 4 種方法里排名第二:

不過,這種使用 NSExpression 表達式來計算最大值方法的一個美中不足是:我們只能得到最新的日期,但卻無法同時得到其所對應的 VictoryStage 對象。

一種解決辦法是,利用上面求得的最新 end 值來再次查詢出對應的 VictoryStage 對象:

func testPerformanceWithFetchExpression() throws {
    // 利用 NSExpression 的 max 函數(shù)來計算最新的 VictoryStage
    measure {
        let req = NSFetchRequest<NSDictionary>(entityName: "VictoryStage")
        req.predicate = .init(format: "project = %@", project)
        let maxExpr = NSExpression(forKeyPath: \VictoryStage.end)
        let maxExprDesc = NSExpressionDescription()
        maxExprDesc.name = "endDay"
        maxExprDesc.expression = NSExpression(forFunction: "max:", arguments: [maxExpr])
        maxExprDesc.expressionResultType = .dateAttributeType
        req.propertiesToFetch = [maxExprDesc]
        req.resultType = .dictionaryResultType

        let result = try! context.fetch(req).first as! [String: Any]
        let endDay = result["endDay"] as! Date
        
        let req2 = VictoryStage.fetchRequest()
        req2.predicate = .init(format: "project = %@ AND end = %@", argumentArray: [project!, endDay])
        req2.fetchLimit = 1
        let mostRecent = try! context.fetch(req2).first
        print("最新 VStage 的日期:\(mostRecent?.end ?? .distantPast)")
        
    }
}

不過這種方法需要 2 次查表,所以會帶來一定性能上的損失。它的耗時為 0.000688 秒,比原先慢了將近一倍:


有些小伙伴們可能會認為,我們可以通過同時獲取最新日期和對應對象的 ID 來找到最新日期對應的 VictoryStage 對象:

let maxEndExpr = NSExpression(forFunction: "max:", arguments: [NSExpression(forKeyPath: "end")])
let maxEndExprDesc = NSExpressionDescription()
maxEndExprDesc.name = "maxEnd"
maxEndExprDesc.expression = maxEndExpr
maxEndExprDesc.expressionResultType = .dateAttributeType

// 2. 定義對象的唯一標識符(如 objectID)
let objectIDExpr = NSExpression(forKeyPath: \VictoryStage.objectID)
let objectIDExprDesc = NSExpressionDescription()
objectIDExprDesc.name = "objectID"
objectIDExprDesc.expression = objectIDExpr
objectIDExprDesc.expressionResultType = .objectIDAttributeType

req.propertiesToFetch = [maxEndExprDesc, objectIDExprDesc]
let results = try context.fetch(req)

但遺憾的是,這種方法行不通。原因是:VictoryStage.objectID 屬性是 CoreData 創(chuàng)建的一個“虛擬”屬性,它實際不存在于數(shù)據(jù)庫 VictoryStage 的表結構中,而我們無法用 NSExpression 去操作這種“虛擬”屬性,除非我們的托管對象中有一個持久的 ID 存在于數(shù)據(jù)庫的表中。


5. 孰是孰非?

現(xiàn)在,我們已經(jīng)分別實現(xiàn)了計算 CoreData 字段最大值的四種方法,最后我們再簡單聊聊它們有哪些明顯的優(yōu)點和缺點:

  1. CoreData 關系屬性(Relation Property)排序;
  2. NSArray 排序;
  3. CoreData Fetch 請求;
  4. NSExpression 表達式;

在上面這些方法中,前三種基本都是排序,只是執(zhí)行排序的層面不一樣(Array、NSArray、Sqlite),而最后一種則是實打實的用 max: 函數(shù)計算最大值。

第一種方法最慢,但實現(xiàn)起來最簡單。第二種方法也比較簡單,但效率有了數(shù)量級的提升。第三種方法速度最快,實現(xiàn)也不算麻煩,是一個不錯的選擇。最后一種方法對于這種簡單的求最大值問題略顯繁瑣,但它能擴展到更復雜的計算場景中,其擴展性和靈活性最高。

當然,大家還可以各顯神通實現(xiàn)自己獨特的算法。若如此,本篇拋磚引玉的目的就美美的達到了。

那么,看完上面這 4 種算法后,大家又作何感想呢?是不是都有種躍躍欲試的“趕腳”呢?棒棒噠!??

總結

在本篇博文中,我們討論了如何用 NSExpression 表達式來計算 CoreData 托管類字段的最大值,我們最后還對所有 4 種方法的孰是孰非做了總結。

感謝觀賞,再會啦!8-)

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容