使用CKFetchRecordZoneChangesOperation时App被杀后的同步问题求解
解决CKFetchRecordZoneChangesOperation令牌与本地数据不一致的痛点
先聊聊你遇到的这个棘手场景:
你用
CKFetchRecordZoneChangesOperation拉取CloudKit变更,已经拿到了所有CKRecords,但还没来得及写入本地缓存,却先在recordZoneChangeTokensUpdatedBlock里把新的变更令牌存了起来。结果用户直接杀了App,进程终止。等重启后,拿着这个“超前”的令牌去拉取变更,服务器说之后没新内容,可上次那些没落地的记录就这么丢了。
确实,有人会说“等记录写完本地再存令牌”,但这个方案在实际场景里有两个致命问题:
- 首次启动要拉大量数据时,全存在内存里再批量写入,很容易爆内存;
recordChangedBlock和recordZoneChangeTokensUpdatedBlock是CloudKit驱动的异步回调,要协调它们的执行顺序,写起来又复杂又容易出bug——毕竟你没法控制CloudKit什么时候调哪个block。
给你几个经过实践验证的解决方案,按需选:
1. 分段拉取+分段确认(最推荐)
利用CKFetchRecordZoneChangesOperation的resultsLimit属性,把大批次的记录拆成小批量处理:
- 给
resultsLimit设一个合理值(比如50或100,根据你单条记录的大小调整),每次只拉一小段数据; - 在
recordChangedBlock里收到这批记录后,立刻写入本地数据库; - 等这批记录全部写入成功,再在
recordZoneChangeTokensUpdatedBlock里缓存当前的临时令牌; - 就算中途App崩了,下次启动用上次成功缓存的令牌继续拉,之前没处理的分段会被重新拉取,不会丢数据。
这个方法既解决了内存问题,又不用搞复杂的同步逻辑——每一段的记录落地和令牌缓存是绑定的,逻辑清晰。
2. 双令牌机制:待确认令牌+正式令牌
如果实在不能分段拉取(比如某些业务场景要求全量同步),可以用双令牌的思路:
- 本地维护两个令牌:
currentValidToken(已经确认对应数据都落地的正式令牌)和pendingToken(刚从服务器拿到的、待确认的临时令牌); - 在
recordZoneChangeTokensUpdatedBlock里,先把新令牌存成pendingToken,别直接覆盖currentValidToken; - 只有当所有拉到的
CKRecords100%成功写入本地后,再把pendingToken替换成currentValidToken并持久化; - 要是App中途挂了,下次启动还是用
currentValidToken去拉,那些没落地的记录会被重新获取回来。
3. 串行队列做同步控制
如果需要更精细的流程把控,可以把记录写入和令牌操作放到同一个串行队列里,确保顺序:
// 串行队列保证操作顺序 let syncQueue = DispatchQueue(label: "com.yourapp.cloudkit.syncQueue") var pendingRecords: [CKRecord] = [] let changeOperation = CKFetchRecordZoneChangesOperation(recordZoneIDs: [yourZoneID], optionsByRecordZoneID: zoneOptions) changeOperation.recordChangedBlock = { record in syncQueue.async { pendingRecords.append(record) } } changeOperation.recordZoneChangeTokensUpdatedBlock = { token, _, _ in syncQueue.async { // 先把所有待处理记录写入本地 self.writePendingRecordsToLocal(pendingRecords) // 写入成功后再持久化令牌 self.saveTokenToLocal(token) // 清空待处理列表 pendingRecords.removeAll() } } changeOperation.fetchRecordZoneChangesCompletionBlock = { error in if let error = error { // 同步失败,清空待处理记录,放弃待确认令牌 syncQueue.async { pendingRecords.removeAll() } // 这里可以加错误重试逻辑 } } CKContainer.default().publicCloudDatabase.add(changeOperation)
这个方式通过串行队列强制让记录写入在令牌缓存之前执行,避免异步回调乱序导致的问题。
内容的提问来源于stack exchange,提问作者lastcc
相关产品推荐
相关产品推荐

