CloudKit Fetch操作返回旧数据集的同步问题排查
CloudKit 同步后查询不到更新的问题排查与修复
看起来你在CloudKit和本地存储同步时遇到了一个典型问题:执行完新增/删除操作后,立刻查询却拿不到最新的记录。我帮你梳理几个关键问题点和修复方案:
1. 确保修改操作真正完成后再执行查询
你的saveUpdateCloud函数必须保证CKModifyRecordsOperation完全执行完毕才触发回调。如果操作还在CloudKit的队列中等待执行,就立刻调用fetchFromCloud,自然会拿到旧数据。
检查你的saveUpdateCloud实现,确保它正确设置了操作完成回调并加入到操作队列:
static func saveUpdateCloud(petsToSave: [CKRecord], recordIDsToDeleve: [CKRecordID], completion: @escaping (Bool, NSError?) -> ()) { let modifyOperation = CKModifyRecordsOperation(recordsToSave: petsToSave, recordIDsToDelete: recordIDsToDeleve) // 设置合适的保存策略,比如只同步修改过的字段 modifyOperation.savePolicy = .changedKeys // 仅在操作彻底完成(成功/失败)后调用回调 modifyOperation.modifyRecordsCompletionBlock = { savedRecords, deletedIDs, error in if let err = error as NSError? { completion(false, err) } else { completion(true, nil) } } // 将操作添加到CloudKit数据库的操作队列 ArendK9DB.share.privateDB.add(modifyOperation) }
2. 查询前清空本地数组,避免旧数据干扰
在fetchFromCloud的回调中,你直接往pets数组append新记录,但之前的sync函数清空数组后,后续查询可能会叠加旧数据。每次查询前先清空数组:
fetchFromCloud(){ (r , e) -> () in print("DEBUG Loading new CloudKit array at \(Date())") self.pets.removeAll() // 先清空旧数据,确保只保留最新查询结果 if e != nil { print(e?.localizedDescription ?? "General Query Error: No Description") } else { for record in r { if let pet = Pet(remoteRecord: record) { self.pets.append(pet) } } // 保存到本地并回调,注意先判断数组非空 print("DEGUG Saving records to local store") if !self.pets.isEmpty { self.pets[0].saveToLocal(petsToSave: self.pets) } print("DEBUG I have a new pet array from CloudKit \(self.pets)") completion(self.pets, nil) } }
3. 强制查询忽略缓存,获取服务器最新数据
CloudKit默认会用缓存加速查询,这可能导致你拿到修改前的旧数据。修改fetchFromCloud中的查询,设置缓存策略为忽略缓存:
static func fetchFromCloud( completion: @escaping ( _ results: [CKRecord], _ error: NSError?) -> ()) { print("Fetch from CloudKit... starting completion handler") let predicate = NSPredicate(value: true) let query = CKQuery(recordType: RemoteRecords.pet, predicate: predicate) // 强制从服务器拉取最新数据,跳过本地缓存 query.cachePolicy = .ignoreCache ArendK9DB.share.privateDB.perform(query, inZoneWith: ArendK9DB.share.zoneID, completionHandler: { (records, error) in if let err = error as NSError? { completion([], err) } else { completion(records ?? [], nil) } }) }
4. 修复本地新增记录的潜在崩溃风险
你创建changedRecord后直接用!强制解包,如果创建失败会导致崩溃。建议添加安全判断:
if localPet.recordName == "Local" { guard let newRecord = CKRecord(recordType: RemoteRecords.pet, zoneID: ArendK9DB.share.zoneID) else { continue // 跳过创建失败的记录 } newRecord[RemotePet.petName] = localPet.petName as NSString newRecord[RemotePet.dob] = localPet.dob as NSDate? newRecord[RemotePet.petSex] = localPet.petSex as NSString? // 处理图片Asset的代码... if let photo = localPet.photo { let imageData = UIImageJPEGRepresentation(photo, 1.0)! let path = Pet.documentsDirectoryPath.appendingPathComponent(Pet.tempImageName) try? imageData.write(to: URL(fileURLWithPath: path), options: [.atomic]) if let fileAsset = CKAsset(fileURL: URL(fileURLWithPath: path)) { newRecord[RemotePet.photo] = fileAsset } } petsToSave.append(newRecord) }
5. 优化删除标记的判断逻辑
通过petName.lowercased().range(of: "delete me")标记删除的方式非常不可靠,建议在你的Pet本地模型中添加isMarkedForDelete: Bool属性,这样判断更准确,也避免误删正常记录。
内容的提问来源于stack exchange,提问作者CliffAnd
相关产品推荐
相关产品推荐

