使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用

ops/2025/1/15 10:23:52/

在这里插入图片描述

概览

在今年的 WWDC 24 中,苹果将 SwiftData 升级为了 2.0 版本。其中对部分已有功能进行了增强的同时也加入了许多全新的特性,比如历史记录追踪History Trace)、“墓碑”(Tombstone)等。

在这里插入图片描述

我们可以利用 History Trace 来跟踪 SwiftData 持久存储中数据的变化,利用令牌我们还可以进一步优化 SwiftData 的使用效率。

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

  • 概览
  • 1. 历史记录追踪机制简介
  • 2. 使用令牌(HistoryToken)过滤历史记录
  • 3. 删除过期的历史记录
  • 总结

相信有了令牌的加持,必将为 SwiftData 历史记录追踪锦上添花、百尺竿头!

闲言少叙,让我们马上开始 History Trace 的优化之旅吧!

Let‘s go!!!😉


1. 历史记录追踪机制简介

简单来说,今年苹果在 WWDC 24 上新祭出的历史记录追踪History Trace)可以让我们更加轻松的监控 SwiftData 持久存储中数据的变化。如此一来,我们即可以非常 nice 的同步模型上下文之间、进程之间以及系统组件与 App 之间的数据变化了。

在这里插入图片描述
在这里插入图片描述

历史记录追踪History Trace)机制专门用来查询 SwiftData 模型数据更改的历史记录,主要来说它有以下几种用途:

  • 了解数据存储何时发生了更改?发生了什么更改?即使记录从数据库中被彻底删除之后仍然可以获取其部分信息(“墓碑”机制);
  • 了解如何使用该信息构建远程服务器同步;
  • 处理进程外的更改事件;

更多关于历史记录追踪机制的介绍,小伙伴们可以移步如下链接观赏精彩的内容:

  • 用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
  • 由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
  • 由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(四)

关于 SwiftData 2.0 中其它新特性的进一步介绍,请小伙伴们猛戳下面的链接观看更多内容:

  • iOS 18 中全新 SwiftData 重装升级,其中一个功能保证你们“爱不释手”

SwiftData 2.0 中,苹果为模型上下文(ModelContext)新增了 fetchHistory() 以及一系列相关的方法专门为历史记录追踪功能而服务:

在这里插入图片描述

利用它们我们只需寥寥几行代码即可监听数据变动,仿佛“樽前月下”:

let mainContext = self.modelContextvar historyDesc = HistoryDescriptor<DefaultHistoryTransaction>()
historyDesc.predicate = #Predicate { trans intrans.author == "BG"
}let transactions = try! mainContext.fetchHistory(historyDesc)
for trans in transactions {for change in trans.changes {let modelID = change.changedPersistentIdentifierguard let changedItem = mainContext.model(for: change.changedPersistentIdentifier) as? Item else { continue }switch change {case .insert(let historyInsert):print("find insert")// 处理记录插入case .update(let historyUpdate):print("find update")// 处理记录更新case .delete(let historyDelete):print("find del")// 处理记录删除@unknown default:fatalError()}}
}

在上面的代码中,我们主要做了这样几件事:

  • 利用模型上下文的 author 属性排除了非后台 ModelContent 修改的历史记录;
  • 通过 Change 记录的 changedPersistentIdentifier 属性抓取了修改后的托管对象;
  • 根据具体的 Change 类型(新增、更改和删除)来做出妥善的后续处理;

虽然上面的代码没有任何问题,不过需要注意的是历史追踪记录本身也是需要存储在持久数据库中的。这意味着:随着 History Trace 的持续监听这些追踪记录会让数据库的体积变得不堪重负,更尴尬的是这些过期的“累赘”往往已经没有再使用的价值了。

那么我们该如何是好呢?

别着急,HistoryToken 可以为我们解忧排愁!

2. 使用令牌(HistoryToken)过滤历史记录

准确的说,历史令牌(HistoryToken)其实是一种协议:

在这里插入图片描述

因为我们往往都是与 SwiftData 中的默认存储(Store)打交道,所以我们需要使用系统提供的遵循 HistoryToken 协议的实体类型:DefaultHistoryToken。

在这里插入图片描述


如果大家需要为 SwiftData 自定义存储添加历史记录追踪支持,请参考如下苹果官网的开发视频:

  • Track model changes with SwiftData history

默认历史令牌本身包含历史追踪记录发生的时间,并且其本身遵守可比较(Comparable)协议,所以我们可以比较两个令牌来判断它们的时效性。

@State var historyToken: DefaultHistoryToken?private func handleChangeInMainContext() {let mainContext = modelContextvar historyDesc = HistoryDescriptor<DefaultHistoryTransaction>()if let token = historyToken {historyDesc.predicate = #Predicate { trans in// 排除旧令牌对应的历史记录trans.author == "BG" && trans.token > token}} else {historyDesc.predicate = #Predicate { trans intrans.author == "BG"}}let transactions = try! mainContext.fetchHistory(historyDesc)for trans in transactions {for change in trans.changes {// 处理具体的历史追踪记录}}// 保存最后一个历史令牌historyToken = transactions.last?.token
}

如上代码所示:我们在每次监听历史追踪记录后还不忘保存最后一个历史令牌。这样做的好处是,我们就可以在下一次抓取历史追踪记录时排除过期的记录了。

3. 删除过期的历史记录

虽然上面我们已经能够悠然自得的通过历史令牌来排除过期的历史追踪记录,但是这些“累赘”还仍然顽强的占据着 SwiftData 持久存储数据库的宝贵空间。长此以往,这些无用的历史记录可能会让我们的 App 臃肿不堪。

其实,SwiftData 提供了专门的 deleteHistory() 方法来删除指定的历史追踪记录:

在这里插入图片描述

一般情况下,在过去监听中已经被抓取过的历史追踪记录我们都可以统统删掉:

extension ModelContext {func deleteTransactions(before token: DefaultHistoryToken) throws {var descriptor = HistoryDescriptor<DefaultHistoryTransaction>()descriptor.predicate = #Predicate {// 删除早于指定 token 的所有历史追踪记录$0.token < token}try self.deleteHistory(descriptor)}
}

可以看到,我们删除历史记录的代码非常简单直接:只需将数据库中比指定 token 要早的历史追踪记录删除即可。

现在,我们适配 SwiftData 的 App 在使用 History Trace 时变得又快又好,底层的数据库也始终保持着苗条的身材,棒棒哒!!!

总结

在本篇博文中,我们讨论了如何使用令牌进一步优化 SwiftData 2.0历史记录追踪机制的使用;我们随后还介绍了删除数据库中无用记录的方法。

感谢观赏,再会啦!😎


http://www.ppmy.cn/ops/109631.html

相关文章

低代码开发平台:高效开发的新引擎

在数字化时代&#xff0c;软件开发的需求日益增长&#xff0c;传统的开发方式因其复杂性和耗时性已难以满足市场的快速变化。低代码开发平台作为一种创新的解决方案&#xff0c;正逐渐成为软件开发领域的新宠。勤研低代码平台通过图形化界面和预制模块&#xff0c;使得应用开发…

环境搭建---部署rabbitmq集群

rabbitmq下载&#xff1a;https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.34/rabbitmq-server-generic-unix-3.8.34.tar.xz erlang下载&#xff1a;https://github.com/erlang/otp/releases/download/OTP-24.3.4.1/otp_src_24.3.4.1.tar.gz 配置主机名 …

51. 数组中的逆序对

comments: true difficulty: 困难 edit_url: https://github.com/doocs/leetcode/edit/main/lcof/%E9%9D%A2%E8%AF%95%E9%A2%9851.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9/README.md 面试题 51. 数组中的逆序对 题目描述 在数组中的两个数字&…

49. 字母异位词分组

刷题找工作&#xff01;&#xff01;冲冲冲&#xff01; 题目链接 . - 力扣&#xff08;LeetCode&#xff09; 自己的思路 对字符串进行排序&#xff0c;查看是否存在map以排序后数组作为key的项&#xff0c;如果有&#xff0c;加入后面的列表中&#xff0c;如果没有&#xf…

C++复习day05

类和对象 1. 面向对象和面向过程的区别是什么&#xff1f;&#xff08;开放性问题&#xff09; 1. **抽象级别**&#xff1a;- **面向对象**&#xff1a;以对象&#xff08;数据和方法的集合&#xff09;为中心&#xff0c;强调的是数据和行为的封装。- **面向过程**&#xf…

JAVA基础:内部类,匿名内部类,对象大小比较,Box容器迭代器封装实现

1 内部类 1.1 内部类编写 内部类就是内部的一个类 在什么内部呢&#xff1f; 类的内部 &#xff0c; 相当于类的一个特殊成员&#xff0c;具备类成员的访问特点 &#xff0c;称为&#xff1a;成员内部类 4种访问权限 可以使用static关键字修饰 方法的内部&#xff1a;类似于…

电脑驱动分类

电脑驱动程序&#xff08;驱动程序&#xff09;是操作系统与硬件设备之间的桥梁&#xff0c;用于使操作系统能够识别并与硬件设备进行通信。以下是常见的驱动分类&#xff1a; 1. 设备驱动程序 显示驱动程序&#xff1a;控制显卡和显示器的显示功能&#xff0c;负责图形渲染和…

目标检测-YOLOv10

YOLOv10 是 YOLO 系列的最新版本&#xff0c;进一步推动了目标检测技术的发展。它在前代&#xff08;YOLOv9&#xff09;的基础上进行了更多优化和改进&#xff0c;使得模型在复杂场景、实时性以及精度方面取得了更高的突破。YOLOv10 将高效的架构设计与新颖的技术结合&#xf…