如何安全清理CoreData持久化历史不影响CloudKit同步正确性
我们当前通过NSPersistentCloudKitContainer实现本地CoreData与CloudKit的集成。
启用持久化历史跟踪的原因
参考相关技术问题描述,为避免同步异常,我们需要开启NSPersistentHistoryTrackingKey。
持久化历史清理需求
根据苹果官方CoreData相关文档说明,持久化历史跟踪产生的历史数据需要手动执行清理操作。
但目前我们尚未明确如何安全执行历史清理,避免影响CloudKit同步的正确性,为此我们搭建了如下测试环境开展验证:
- 启动模拟器,在模拟器中执行数据插入操作
- 启动真机,真机将因模拟器侧的数据插入操作接收静默推送通知
- 模拟器与真机运行完全相同的代码逻辑
- 每次在模拟器插入数据后,观察真机的同步表现
测试1:处理完成后立即清理所有历史数据
测试代码如下:
@objc func storeRemoteChange(_ notification: Notification) { // Process persistent history to merge changes from other coordinators. historyQueue.addOperation { self.processPersistentHistory() } } /** Process persistent history, posting any relevant transactions to the current view. */ private func processPersistentHistory() { backgroundContext.performAndWait { // Fetch history received from outside the app since the last token let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest! historyFetchRequest.predicate = NSPredicate(format: "author != %@", appTransactionAuthorName) let request = NSPersistentHistoryChangeRequest.fetchHistory(after: lastHistoryToken) request.fetchRequest = historyFetchRequest let result = (try? backgroundContext.execute(request)) as? NSPersistentHistoryResult guard let transactions = result?.result as? [NSPersistentHistoryTransaction] else { return } ... // Update the history token using the last transaction. lastHistoryToken = transactions.last!.token // Remove history before the last history token let purgeHistoryRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: lastHistoryToken) do { try backgroundContext.execute(purgeHistoryRequest) } catch { error_log(error) } } }
测试观察结果:真机获取的CloudKit同步信息异常,出现数据重复或数据被删除的问题。
我们对该问题的假设如下:
- 持久化历史数据在同一设备的多个持久化协调器之间共享
- 业务层可见的协调器完成事务处理后,记录
lastHistoryToken,随即清理该token之前的所有历史 - 但
CloudKit同步使用了一个系统层面不可见的内置协调器,极有可能在清理历史时,该协调器尚未处理被删除的历史事务 - 最终导致
CloudKit同步真机数据时缺少必要的事务历史,引发数据异常
测试2:处理完成后仅清理2分钟前的历史数据
我们调整清理逻辑,仅删除生成时间早于2分钟的事务历史,核心代码如下:
// Remove history older than 2 minutes. let date = Date(timeMillis: Date.currentTimeMillis - 2*60*1000) let purgeHistoryRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: date) do { try backgroundContext.execute(purgeHistoryRequest) } catch { error_log(error) }
测试观察结果:
- 若两次
storeRemoteChange触发的时间间隔小于2分钟,真机可获取正确的CloudKit同步信息 - 若两次
storeRemoteChange触发的时间间隔大于2分钟,真机将获取错误的CloudKit同步信息,同样出现数据重复或丢失问题
问题汇总
参考相关技术问答给出的建议:“在持久化历史处理完成7天后再执行清理是安全的”。
但针对单用户双设备的场景:
- 用户在常用设备A上频繁执行读写操作
- 用户间隔7天才在闲置设备B上启动同一款应用
按照测试2的观测结论,是否意味着设备B会出现CloudKit同步信息错误?如果结论成立,请问如何正确执行持久化历史清理操作,才不会影响CloudKit同步的正确性?
附:测试2复现步骤
- 下载并运行苹果官方提供的CoreData CloudKit同步示例项目
- 将项目中的
CoreDataStack.swift替换为自定义实现文件,该文件包含Date辅助方法与2分钟历史清理逻辑 - 在模拟器中点击右上角按钮创建1条记录,可观察到真机同步得到该条记录
- 等待3分钟后再次点击右上角按钮创建记录,此时模拟器中共有2条记录,但真机中的数据会完全丢失
测试现象参考:左侧为真机界面,右侧为模拟器界面,真机数据完全丢失,与模拟器数据状态不一致
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng
相关产品推荐
相关产品推荐

