本文档面向从 PersistentHistoryTrackingKit V1 迁移到 V2 的项目。
V2 不是兼容性小版本,而是一次完整重写。它引入了新的并发模型、扩展点,以及不同的清理语义。
满足以下条件时,建议迁移到 V2:
- 目标平台已经是 iOS 17+、macOS 14+、tvOS 17+ 或 watchOS 10+
- 准备切换到 Swift 6
- 希望使用 actor 架构和 async Hook API
- 希望获得 Observer Hook、Merge Hook 与 tombstone 支持
以下情况建议继续留在 V1:
- 仍需支持旧系统
- 暂时不准备迁移到 Swift 6
- 更希望保留 V1 的 fetch / merge / cleaner 定制模型
- 以 fetcher / merger / cleaner 管线为核心
- 主要通过协议注入进行自定义,例如
TransactionMergerProtocol - 提供 deduplicator 扩展点
- 通过任务管理处理通知监听
- 提供可直接调用的 manual cleaner
- 基于 actor 完全重写
- 以
HookRegistryActor和TransactionProcessorActor为核心 - 使用 async Observer Hook 和 Merge Hook 取代协议注入
- 新增分组 observer 回调与 tombstone 支持
- 使用
ManualCleanerActor执行手动清理
- 面向 Swift 5 时代的 API 设计
- 具体支持平台取决于你正在使用的 V1 发布版本
- Swift 6
- iOS 17+
- macOS 14+
- macCatalyst 17+
- tvOS 17+
- watchOS 10+
- 依赖
swift-async-algorithms
- 依赖
CoreDataEvolution - 通过
@NSModelActor使用 actor 化的 Core Data 辅助能力
| V1 概念 | V2 对应方式 |
|---|---|
| 协议式 merger | registerMergeHook |
| 协议式 deduplicator | registerMergeHook |
| 合并流程中的只读副作用 | registerObserver |
| 可调用 manual cleaner | cleanerBuilder() 返回 ManualCleanerActor |
| 通知驱动的任务管理 | 内部 actor 驱动处理 |
performAndWaitWithResult 辅助方法 |
在常规 V2 用法里通常不再需要 |
let kit = PersistentHistoryTrackingKit(
container: container,
contexts: [container.viewContext],
currentAuthor: "MainApp",
allAuthors: ["MainApp", "WidgetExtension"],
userDefaults: userDefaults,
cleanStrategy: .byDuration(seconds: 60 * 60 * 24 * 7),
logLevel: 1
)如果你在 V1 中定制过 merge 行为,通常是通过实现 TransactionMergerProtocol。
请改为注册 Merge Hook:
let hookID = await kit.registerMergeHook { input in
for context in input.contexts {
await context.perform {
// 自定义 merge 逻辑
}
}
return .goOn
}如果你的 Hook 已经完整处理了合并流程,并且不希望再执行默认 merge,请返回 .finish。
V1 提供了独立的 deduplicator 协议。
V2 不再提供单独的 deduplicator 协议。请把去重逻辑放进 Merge Hook:
await kit.registerMergeHook { input in
for context in input.contexts {
await context.perform {
// 去重逻辑
}
}
return .goOn
}V1 没有 V2 这种 observer hook 模型。
可以使用 Observer Hook 做日志、统计、缓存失效、通知派发等只读操作:
let id = await kit.registerObserver(entityName: "Person", operation: .insert) { contexts in
for context in contexts {
print(context.objectIDURL)
}
}Observer 回调会按照“事务 + 实体名 + 操作类型”分组。
这是 V2 相对 V1 的一个明确能力升级。
如果模型中的属性启用了 preservesValueInHistoryOnDeletion,删除类 observer hook 可以通过
HookContext.tombstone 读取这些保留下来的值。
这是迁移时最重要的行为差异之一。
maximumDuration会参与清理就绪判断- 即便有些 author 还没 merge,只要超过设定时长,也可能被强制清理
- 自动清理采用保守策略
- 只有当前
cleanStrategy允许时才会尝试清理 - 只有所有非 batch author 都记录了 merge 时间戳后才会执行自动清理
- 只要任何一个必要 author 缺失时间戳,就会跳过自动清理
这种设计对多 author、App Group、扩展场景更安全。
在当前 V2 实现里,maximumDuration 被保留给未来的清理就绪策略,不再作为隐式的强制清理回退逻辑。
如果你以前依赖 V1 的强制清理行为,现在建议改为:
- 显式使用手动清理
- 重新审视
allAuthors的组成 - 把只写不读的参与者放进
batchAuthors
V1 关于 CloudKit 的使用建议在方向上仍然成立,但 V2 的边界更清晰:
- 当 CloudKit 依赖 persistent history 时,不要激进清理
- 除非你非常明确该工作流,否则不要开启
includingCloudKitMirroring - 有 CloudKit 时优先使用时间跨度足够长的 duration 策略
let cleaner = kit.cleanerBuilder()
cleaner()let cleaner = kit.cleanerBuilder()
Task {
await cleaner.clean()
}logLevel 在 V2 里仍然保留,但日志行为更简单,并且在初始化时固定。
如果你在 V1 里使用了自定义 logger,迁移通常比较直接:
struct MyLogger: PersistentHistoryTrackingKitLoggerProtocol {
func log(type: PersistentHistoryTrackingKitLogType, message: String) {
Logger.log(type, message)
}
}- 确认部署目标和 Swift 版本满足 V2 要求。
- 把 V1 的 merger / deduplicator 定制迁移到 Merge Hook。
- 把只读监控逻辑迁移到 Observer Hook。
- 重新审视
allAuthors和batchAuthors。 - 如果之前依赖强制清理,重新设计清理策略。
- App Group 场景使用共享
UserDefaults。 - 按当前并行测试基线运行测试,并保持 Core Data 并发断言开启。
当前仓库的全量测试已经切换为并行执行。
- 命令行:
swift test --parallel - 或直接使用:
./test.sh - Xcode 中也支持并行测试
TestModelBuilder.createContainer会刻意串行化,因为并发初始化NSPersistentContainer并加载 store 时,Core Data 内部可能出现崩溃