11

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

谢谢窥视!

4

3 回答 3

25

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

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 中。一个可能更好的解决方案是将每个调用包装在 a 中NSBlockOperation并将它们添加到 NSOperationQueue 中,其中 saveOperation 依赖于 fetchOperation。

如果您只更新一条记录,这部分代码将用于 aCKModifyRecordsOperation并且不需要:

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

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

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 演示应用程序之外,还没有太多示例代码。希望这可以帮助。

于 2014-07-10T07:04:06.850 回答
22

Generally speaking, you have unitary methods (like saveRecord), which deal with only one record at a time, and mass operations (like CKModifyRecordsOperation), which deal with several records at the same time.

These save operations can be used to save records, or to update records (that is, fetch them, apply changes to them, and then save them again).


SAVE examples:

You create a record and want to save it to 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!")
  }
})

You create a bunch of records and you want to save them all at once:

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)

UPDATE examples:

Usually, you have 3 cases in which you want to update records :

  1. you know the record identifier (generally the recordID.recordName of the record you want to save: in that case, you will use methods fetchRecordWithID and then saveRecord
  2. you know there is a unique record to update but you don't know its recordID: in that case, you will use a query with method performQuery, select the (only) one you need and again saveRecord

  3. you are dealing with many records that you want to update: in that case, you will use a query to fetch them all (performQuery), and a CKModifyRecordsOperation to save them all.

Case 1 - you know the unique identifier for the record you want to update:

    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!")
                }
            })
        }
    })

Case 2 - you know there is a record corresponding to your conditions, and you want to update it:

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!")
                   }
             })
         }
     }
})

Case 3 - you want to grab multiple records, and update them all at once:

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)
     }
})

Now, that was a lenghty answer to your question, but your code mixed both a unitary save method with a CKModifyRecordsOperation.

Also, you have to understand that, each time you create a CKRecord, CloudKit will give it a unique identifier (the record.recordID.recordName), unless you provide one yourself. So you have to know if you want to fetch an existing record, or create a new one before calling all these beautiful methods :-) If you try to create a new CKRecord using the same unique identifier as another one, then you'll most certainly get an error.

于 2015-04-24T15:05:49.930 回答
1

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

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

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

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

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

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

于 2015-01-24T21:16:18.333 回答