CloudKit使用自定义CKRecord.ID可删除记录却无法修改的问题
问题原因
- CloudKit 每条记录除了自定义的ID外,还内置了服务端维护的变更标记(change tag),用来做并发修改冲突校验。删除操作仅需校验记录ID匹配即可执行,因此你用正确的ID可以完成删除。
- 你修改时直接通过
CKRecord(recordType: recordType, recordID: recordId)创建了全新的记录实例,这个实例没有携带服务端当前对应记录的有效change tag。CloudKit默认的保存策略是ifServerRecordUnchanged,会校验你提交的记录携带的change tag和服务端最新版本是否一致:如果不一致、或者像你的情况根本没有change tag,就会判定为「记录已存在且版本不匹配」,抛出Server Record Changed和record to insert already exists错误。
解决方案
有两种常用的修复方案:
方案1:强制覆盖服务端版本(适合单用户使用、不会有并发修改的场景)
直接修改CKModifyRecordsOperation的保存策略,跳过change tag校验,强制用你提交的内容覆盖服务端记录。
只要在你的modify方法中添加一行配置即可:
let operation = CKModifyRecordsOperation(recordsToSave: [record], recordIDsToDelete: nil) operation.configuration = configuration // 新增这行,修改保存策略为强制覆盖 operation.savePolicy = .overwriteAll
方案2:先拉取服务端最新记录再修改(适合多端同步、可能存在并发修改的场景)
先通过ID从CloudKit查询到最新的记录实例,在这个实例基础上修改字段内容再提交,此时实例已经携带了正确的change tag,不会触发冲突校验报错。
示例代码逻辑如下:
func modify(_ item: MyItem, completion: @escaping (Result<Int, Error>) -> ()) { let myOwnUUID = item.id let recordId = CKRecord.ID(recordName: myOwnUUID, zoneID: .default) // 先拉取服务端最新记录 ckDatabase.fetch(withRecordID: recordId) { record, error in DispatchQueue.main.async { if let error = error { completion(.failure(error)) return } guard let record = record else { completion(.failure(CloudKitHelperError.recordNotFound)) return } // 在拉取到的记录上修改字段 record.setObject(item.title as __CKRecordObjCValue, forKey: "title") // 再提交修改 let configuration = CKOperation.Configuration() configuration.timeoutIntervalForRequest = 10 configuration.timeoutIntervalForResource = 10 let operation = CKModifyRecordsOperation(recordsToSave: [record], recordIDsToDelete: nil) operation.configuration = configuration operation.modifyRecordsCompletionBlock = { (savedRecords, _, error) in DispatchQueue.main.async { if savedRecords != nil, error == nil { completion(.success(0)) } if let error = error { completion(.failure(error)) } } } ckDatabase.add(operation) } } }
内容的提问来源于stack exchange,提问作者Kaplan
相关产品推荐
相关产品推荐

