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 に保存できます。単一のレコードの場合は を使用できますが、iCloud に保存し直す前に、saveRecord:を使用して変更したいレコードをフェッチする必要があります。fetchRecordWithID:それ以外の場合は、新しい 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 が既に起動されているためです。簡単な解決策は、fetchRecordWithID の completionHandler に saveRecord をネストすることです。おそらくより良い解決策は、各呼び出しを でラップNSBlockOperationし、fetchOperation に依存する saveOperation を使用してそれらを NSOperationQueue に追加することです。

コードのこの部分は a 用でCKModifyRecordsOperationあり、単一のレコードのみを更新する場合は必要ありません。

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

代わりに a を使用する場合はCKModifyRecordsOperation、少なくとも 1 つの完了ブロックを設定し、既存のレコードとの競合が検出されたときにエラーを処理する必要もあります。

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 でレコードを取得していました。同じレコードを複数回更新していて、同期がずれていたことが判明しました。

メインスレッドによって呼び出される update-and-save メソッドがあり、時には急速に呼び出されます。

ブロックを使用してすぐに保存していますが、レコードをすばやく更新していると、次のような状況に到達する可能性があります。

  1. レコードを取得し、インスタンス A' を取得します。
  2. レコードを取得し、インスタンス A'' を取得します。
  3. A' を更新して保存します。
  4. A'' を更新して保存します。

サーバー上でレコードが更新されているため、A'' の更新は失敗します。

更新中の場合は、レコードの更新を待つようにして、これを修正しました。

于 2015-01-24T21:16:18.333 に答える