Swift CloudKit SaveRecord"保存记录错误"

时间:2014-07-06 21:03:35

标签: ios swift ios8 cloudkit

我正在尝试将记录保存到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上得到的)。

谢谢偷看!

3 个答案:

答案 0 :(得分:24)

可以使用CKDatabase的便捷方法saveRecord:或通过CKModifyRecordsOperation将记录保存到iCloud。如果它是单个记录,您可以使用saveRecord:,但需要在将其保存回iCloud之前使用fetchRecordWithID:来修改您想要修改的记录。否则,它只允许您使用新的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)

除了CloudKitAtlas演示应用程序之外,还有很多示例代码,它位于Objective-C中。希望这会有所帮助。

答案 1 :(得分:19)

一般来说,你有单一的方法(比如 saveRecord ),它一次只处理一条记录,而群众操作(喜欢 CKModifyRecordsOperation ),它同时处理几条记录。

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

SAVE示例:

您创建了一条记录,并希望将其保存到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. 你知道有一个唯一的记录要更新,但你不知道它的recordID:在这种情况下,你将使用一个查询方法 performQuery ,选择您需要的(仅)一个 saveRecord

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

  4. 案例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,那么您肯定会收到错误。

答案 2 :(得分:1)

我有同样的错误,但是我已经按照Guto所描述的那样通过ID获取记录了。事实证明我多次更新同一条记录,事情变得不同步。

我有一个更新和保存方法,主线程有时会快速调用它。

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

  1. 获取记录,获取实例A&#39;。
  2. 获取记录,获取实例A&#39;&#39;。
  3. 更新A&#39;并保存。
  4. 更新A&#39;&#39;并保存。
  5. 更新A&#39;&#39;将失败,因为记录已在服务器上更新。

    我通过确保等待更新记录来解决这个问题,如果我正在更新记录中。