“群芳争艳”:CoreData 4 种方法计算最大值的效率比较(下)

在这里插入图片描述

概览

在 CoreData 支持的 App 中,一种常见操作就是计算数据库表中指定字段的最大值(或最小值)。就是这样一种看起来“不足挂齿”的任务,可能稍不留神就会“马失前蹄”。

在这里插入图片描述

在实际的代码中,我们怎样才能既迅速又简洁的找出字段的最大值呢?

在本篇博文中,您将学到如下内容:

相信学完本课后,大家 CoreData 的算法武器库中又会多几种“削铁如泥”的利刃啦。

本文中所有代码的测试环境为:

  • MBA 2022,M2,内存 16 GB
  • macOS 15.3.2(Sequoia)

那还等什么呢?Let‘s find out!!!😉


4. 使用 NSExpression 表达式

除了按时间排序 VictoryStage 数组以外,我们还可以直接利用 NSExpression 表达式自带的 max 函数来计算 VictoryStage 托管对象最新的 end 值。

在这里插入图片描述

大多数小伙伴们可能都对 NSExpression 的使用比较陌生,其实利用 NSExpression 我们可以写出极具灵活性的比较逻辑表达式:

func testPerformanceWithFetchExpression() throws {
    // 利用 NSExpression 的 max 函数来计算最新的 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)")
    }
}

在上面的实现中,我们完成的主要工作是:

  1. 创建一个计算 VictoryStage.end 字段最大值(max:)的 NSExpression 表达式;
  2. 将该表达式应用在 Fetch Request 上;
  3. 调用 CoreData 上下文的 fetch 方法来求得表达式的结果;
  4. 从结果字典中解析最近的日期;

在运行结果中,我们可以看到实际耗时为 0.000389 秒,在 4 种方法里排名第二:

在这里插入图片描述

不过,这种使用 NSExpression 表达式来计算最大值方法的一个美中不足是:我们只能得到最新的日期,但却无法同时得到其所对应的 VictoryStage 对象。

一种解决办法是,利用上面求得的最新 end 值来再次查询出对应的 VictoryStage 对象:

func testPerformanceWithFetchExpression() throws {
    // 利用 NSExpression 的 max 函数来计算最新的 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 创建的一个“虚拟”属性,它实际不存在于数据库 VictoryStage 的表结构中,而我们无法用 NSExpression 去操作这种“虚拟”属性,除非我们的托管对象中有一个持久的 ID 存在于数据库的表中。


5. 孰是孰非?

现在,我们已经分别实现了计算 CoreData 字段最大值的四种方法,最后我们再简单聊聊它们有哪些明显的优点和缺点:

  1. CoreData 关系属性(Relation Property)排序;
  2. NSArray 排序;
  3. CoreData Fetch 请求;
  4. NSExpression 表达式;

在上面这些方法中,前三种基本都是排序,只是执行排序的层面不一样(Array、NSArray、Sqlite),而最后一种则是实打实的用 max: 函数计算最大值。

第一种方法最慢,但实现起来最简单。第二种方法也比较简单,但效率有了数量级的提升。第三种方法速度最快,实现也不算麻烦,是一个不错的选择。最后一种方法对于这种简单的求最大值问题略显繁琐,但它能扩展到更复杂的计算场景中,其扩展性和灵活性最高。

当然,大家还可以各显神通实现自己独特的算法。若如此,本篇抛砖引玉的目的就美美的达到了。

那么,看完上面这 4 种算法后,大家又作何感想呢?是不是都有种跃跃欲试的“赶脚”呢?棒棒哒!💯


想要进一步系统地学习 Swift 开发的小伙伴们,可以来我的《Swift 语言开发精讲》专栏逛一逛哦:

在这里插入图片描述


总结

在本篇博文中,我们讨论了如何用 NSExpression 表达式来计算 CoreData 托管类字段的最大值,我们最后还对所有 4 种方法的孰是孰非做了总结。

感谢观赏,再会啦!😎

基于c++开发的网络嗅探器,重点对TCP、UDP、ARP、IGMP、ICMP 等数据包进行分析,实现捕捉前过滤、数据包统计、流量统计等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于c++开发的网络嗅探器,重点对TCP、UDP、ARP、IGMP、ICMP 等数据包进行分析,实现捕捉前过滤、数据包统计、流量统计等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~ 基于c++开发的网络嗅探器,重点对TCP、UDP、ARP、IGMP、ICMP 等数据包进行分析,实现捕捉前过滤、数据包统计、流量统计等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于c++开发的网络嗅探器,重点对TCP、UDP、ARP、IGMP、ICMP 等数据包进行分析,实现捕捉前过滤、数据包统计、流量统计等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于c++开发的网络嗅探器,重点对TCP、UDP、ARP、IGMP、ICMP 等数据包进行分析,实现捕捉前过滤、数据包统计、流量统计等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大熊猫侯佩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值