【问题标题】:Swift CloudKit SaveRecord "Error saving record"Swift CloudKit SaveRecord“错误保存记录”
【发布时间】:2014-08-27 07:52:59
【问题描述】:

我正在尝试将记录保存到 CloudKit,但出现错误。我在其他地方看到这是一个需要知道如何保存的问题,但我无法让它工作。

    var database:CKDatabase = CKContainer.defaultContainer().publicCloudDatabase
    var aRecord:CKRecord!

    if self.cloudId == nil {
        var recordId:CKRecordID = CKRecordID(recordName: "RecordId")
        self.cloudId = recordId // Setup at top
    }

    aRecord = CKRecord(recordType: "RecordType", recordID: self.cloudId)
    aRecord.setObject(self.localId, forKey: "localId")

    // Set the normal names etc
    aRecord.setObject(self.name, forKey: "name")

    var ops:CKModifyRecordsOperation = CKModifyRecordsOperation()
    ops.savePolicy = CKRecordSavePolicy.IfServerRecordUnchanged

    database.addOperation(ops)
    database.saveRecord(aRecord, completionHandler: { (record, error) in

        if error != nil {
            println("There was an error \(error.description)!")

        } else {
            var theRecord:CKRecord = record as CKRecord
            self.cloudId = theRecord.recordID
        }
    })

这给了我错误:

There was an error <CKError 0x16d963e0: "Server Record Changed" (14/2017); "Error saving record <CKRecordID: 0x15651730; xxxxxx:(_defaultZone:__defaultOwner__)> to server: (null)"; uuid = 369226C6-3FAF-418D-A346-49071D3DD70A; container ID = "iCloud.com.xxxxx.xxxx-2">!

不确定,因为我添加了 CKModifyRecordsOperation。遗憾的是,Apple 的文档中没有示例。我想念那个(你在 MSDN 上得到的)。

谢谢大家!

【问题讨论】:

    标签: ios swift ios8 cloudkit


    【解决方案1】:

    可以使用 CKDatabase 的便捷方法saveRecord: 或通过 CKModifyRecordsOperation 将记录保存到 iCloud。如果是单条记录,您可以使用saveRecord:,但需要先使用fetchRecordWithID: 获取您要修改的记录,然后再将其保存回 iCloud。否则,它只会让您使用新的 RecordID 保存记录。 More here.

    database.fetchRecordWithID(recordId, completionHandler: { record, error in
        if let fetchError = error {
                println("An error occurred in \(fetchError)")
            } else {
                // Modify the record
                record.setObject(newName, forKey: "name")
            } 
    }
    
    
    database.saveRecord(aRecord, completionHandler: { record, error in
        if let saveError = error {
                println("An error occurred in \(saveError)")
            } else {
                // Saved record
            } 
    }
    

    上面的代码只是方向正确,但不会按原样工作,因为当 fetchRecordWithID 的 completionHandler 返回时,saveRecord 已经触发了。一个简单的解决方案是将 saveRecord 嵌套在 fetchRecordWithID 的 completionHandler 中。一个可能更好的解决方案是将每个调用包装在 NSBlockOperation 中,并将它们添加到 NSOperationQueue,其中 saveOperation 依赖于 fetchOperation。

    这部分代码用于CKModifyRecordsOperation,如果您只更新一条记录,则不需要:

    var ops:CKModifyRecordsOperation = CKModifyRecordsOperation()
    ops.savePolicy = CKRecordSavePolicy.IfServerRecordUnchanged
    database.addOperation(ops)
    

    如果您确实使用CKModifyRecordsOperation,则还需要设置至少一个完成块并在检测到与现有记录发生冲突时处理错误:

    let saveRecordsOperation = CKModifyRecordsOperation()
    
    var ckRecordsArray = [CKRecord]()
    // set values to ckRecordsArray
    
    saveRecordsOperation.recordsToSave = ckRecordsArray
    saveRecordsOperation.savePolicy = .IfServerRecordUnchanged
    saveRecordsOperation.perRecordCompletionBlock { record, error in
        // deal with conflicts
        // set completionHandler of wrapper operation if it's the case
    }
    
    saveRecordsOperation.modifyRecordsCompletionBlock { savedRecords, deletedRecordIDs, error in
        // deal with conflicts
        // set completionHandler of wrapper operation if it's the case
    }
    
    database.addOperation(saveRecordsOperation)
    

    除了使用 Objective-C 的 CloudKitAtlas 演示应用程序之外,还没有太多示例代码。希望这会有所帮助。

    【讨论】:

    • 这个答案对我很有帮助,虽然我遇到了一个无法编译的绊脚石,因为 .perRecordCompletionBlock 和 .modifyRecordsCompletionBlock 似乎需要在 {} 之前添加一个 =,所以“saveRecordsOperation.perRecordCompletionBlock = {} "
    • 您是否尝试在保存之前设置适当的 CKRecord recordChangeTag?这样您就可以避免致电fetchRecordWithID 并且可以减少周转时间
    【解决方案2】:

    一般来说,您有 unitary 方法(如 saveRecord),它一次只处理一条记录,以及 大量操作(像CKModifyRecordsOperation),同时处理多条记录。

    这些保存操作可用于保存 记录,或更新 记录(即获取它们,对其应用更改,然后再次保存)。


    保存示例:

    您创建了一条记录并希望将其保存到 CloudKit DB:

    let database = CKContainer.defaultContainer().publicCloudDatabase
    var record = CKRecord(recordType: "YourRecordType")
    database.saveRecord(record, completionHandler: { (savedRecord, saveError in
      if saveError != nil {
        println("Error saving record: \(saveError.localizedDescription)")                
      } else {
        println("Successfully saved record!")
      }
    })
    

    您创建了一堆记录,并且想要一次保存它们:

    let database = CKContainer.defaultContainer().publicCloudDatabase
    
    // just an example of how you could create an array of CKRecord
    // this "map" method in Swift is so useful    
    var records = anArrayOfObjectsConvertibleToRecords.map { $0.recordFromObject }
    
    var uploadOperation = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
    uploadOperation.savePolicy = .IfServerRecordUnchanged // default
    uploadOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
      if error != nil {
          println("Error saving records: \(error.localizedDescription)")                            
      } else {
          println("Successfully saved records")
      }
    }
    database.addOperation(uploadOperation)
    

    更新示例:

    通常,您有 3 种情况需要更新记录:

    1. 您知道记录标识符(通常是您要保存的记录的recordID.recordName:在这种情况下, 您将使用方法 fetchRecordWithID 然后 saveRecord
    2. 您知道有一条唯一记录要更新,但您不知道它的记录 ID:在这种情况下,您将使用带有方法的查询 performQuery,选择您需要的(唯一)一个,然后再次saveRecord

    3. 您正在处理许多要更新的记录:在这种情况下,您将使用查询来获取所有记录 (performQuery),以及一个 CKModifyRecordsOperation 来保存它们。

    案例 1 - 您知道要更新的记录的唯一标识符:

        let myRecordName = aUniqueIdentifierForMyRecord
        let recordID = CKRecordID(recordName: myRecordName)
    
        database.fetchRecordWithID(recordID, completionHandler: { (record, error) in
            if error != nil {
                println("Error fetching record: \(error.localizedDescription)")
            } else {
                // Now you have grabbed your existing record from iCloud
                // Apply whatever changes you want
                record.setObject(aValue, forKey: attributeToChange)
    
                // Save this record again
                database.saveRecord(record, completionHandler: { (savedRecord, saveError) in
                    if saveError != nil {
                    println("Error saving record: \(saveError.localizedDescription)")
                    } else {
                    println("Successfully updated record!")
                    }
                })
            }
        })
    

    案例 2 - 你知道有一条记录对应你的条件,你想更新它:

    let predicate = yourPredicate // better be accurate to get only the record you need
    var query = CKQuery(recordType: YourRecordType, predicate: predicate)
    database.performQuery(query, inZoneWithID: nil, completionHandler: { (records, error) in
         if error != nil {
                    println("Error querying records: \(error.localizedDescription)")                    
         } else {
             if records.count > 0 {
                 let record = records.first as! CKRecord
                 // Now you have grabbed your existing record from iCloud
                 // Apply whatever changes you want
                 record.setObject(aValue, forKey: attributeToChange)
    
                 // Save this record again
                 database.saveRecord(record, completionHandler: { (savedRecord, saveError in
                       if saveError != nil {
                         println("Error saving record: \(saveError.localizedDescription)")                
                       } else {
                         println("Successfully updated record!")
                       }
                 })
             }
         }
    })
    

    案例 3 - 您想要获取多条记录,并一次更新它们:

    let predicate = yourPredicate // can be NSPredicate(value: true) if you want them all
    var query = CKQuery(recordType: YourRecordType, predicate: predicate)
    database.performQuery(query, inZoneWithID: nil, completionHandler: { (records, error) in
         if error != nil {
                    println("Error querying records: \(error.localizedDescription)")                    
         } else {
                 // Now you have grabbed an array of CKRecord from iCloud
                 // Apply whatever changes you want
                 for record in records {                 
                     record.setObject(aValue, forKey: attributeToChange)
                 }
                 // Save all the records in one batch
                 var saveOperation = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
                 saveOperation.savePolicy = .IfServerRecordUnchanged // default
                 saveOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
                      if error != nil {
                          println("Error saving records: \(error.localizedDescription)")                            
                      } else {
                          println("Successfully updated all the records")
                      }
                 }
                 database.addOperation(saveOperation)
         }
    })
    

    现在,这是对您问题的一个冗长的答案,但是您的代码将单一保存方法与 CKModifyRecordsOperation 混合在一起。

    另外,您必须了解,每次创建 CKRecord 时,CloudKit 都会给它一个唯一标识符(record.recordID.recordName),除非您自己提供。因此,在调用所有这些漂亮的方法之前,您必须知道是要获取现有记录还是创建新记录 :-) 如果您尝试使用与另一个相同的唯一标识符创建新的 CKRecord,那么您肯定会收到错误。

    【讨论】:

    • 非常感谢您提供的示例,它们确实帮助我掌握了窍门!
    • 很高兴它帮助了你:-)
    • 很好的答案,帮助我了解 cloudKitOperations 的工作原理
    • 很好的答案,我认为这应该是公认的答案
    【解决方案3】:

    我遇到了同样的错误,但我已经按照 Guto 的描述按 ID 获取记录。结果我多次更新同一个记录,事情变得不同步了。

    我有一个由主线程调用的更新和保存方法,有时很快。

    我正在使用块并立即保存,但如果您快速更新记录,您可能会遇到以下情况:

    1. 获取记录,获取实例 A'。
    2. 获取记录,获取实例 A''。
    3. 更新 A' 并保存。
    4. 更新 A'' 并保存。

    更新 A'' 将失败,因为记录已在服务器上更新。

    我通过确保在更新记录时等待更新记录来解决此问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-28
      • 1970-01-01
      • 2016-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多