Swift / CloudKit:更改记录后,上传触发“服务记录已更改”

时间:2016-08-25 23:18:03

标签: swift cloudkit ckerror

我正在尝试将CKReference添加到云工具包中的记录中,但尝试会一直触发“服务记录已更改”。从我的println已显示的控制台消息(控制台消息和代码如下) ),我正在上传带有0个引用的记录,然后当我附加引用时,我看到尝试上传带有1个引用的记录。然后我收到了错误。

据我所知,“服务记录已更改”不应被触发,因为参考列表中的值已更改(记录中有一个完整的额外字段)。即使我在在开发模式下,我手动为参考列表创建了键值字段,因为当引用列表为空时,第一个记录上载不包括该字段(上传空数组会导致另一个错误)。

我将在控制台消息之后按相关性顺序包含代码(您将能够看到大部分println)。整个项目在github上,如果需要,我可以链接到它或包含更多代码。

相关控制台:

char charDigit = '0' + digit;// to convert it to char

CrewParticipant的代码:

name was set
uploading TestCrewParticipant
with 0 references
if let projects
upload succeeded: TestCrewParticipant
attaching reference
adding TestVoyage:_d147aa657fbf2adda0c82bf30d0e29a9 from guard
references #: Optional(1)
uploading TestCrewParticipant
with 1 references
if let projects
success: 1
uploading TestCrewParticipant
with 1 references
if let projects
success: 1
local storage tested: TestCrewParticipant
u!error for TestCrewParticipant
CKError: <CKError 0x7fcbd05fa960: "Server Record Changed" (14/2004); server message = "record to insert already exists"; uuid = 96377029-341E-487C-85C3-E18ADE1119DF; container ID = "iCloud.com.lingotech.cloudVoyageDataModel">
u!error for TestCrewParticipant
CKError: <CKError 0x7fcbd05afb80: "Server Record Changed" (14/2004); server message = "record to insert already exists"; uuid = 3EEDE4EC-4BC1-4F18-9612-4E2C8A36C68F; container ID = "iCloud.com.lingotech.cloudVoyageDataModel">
passing the guard 
上传发生的

通用代码(适用于其他几个类):

/**
 * This array stores a conforming instance's CKReferences used as database
 * relationships. Instance is owned by each record that is referenced in the
 * array (supports multiple ownership)
 */
var references: [CKReference] { return associatedProjects ?? [CKReference]() }

// MARK: - Functions

/**
 * This method is used to store new ownership relationship in references array,
 * and to ensure that cloud data model reflects such changes. If necessary, ensures
 * that owned instance has only a single reference in its list of references.
 */
mutating func attachReference(reference: CKReference, database: CKDatabase) {
print("attaching reference")
    guard associatedProjects != nil else {
print("adding \(reference.recordID.recordName) from guard")
        associatedProjects = [reference]
        uploadToCloud(database)
        return
    }
print("associatedProjects: \(associatedProjects?.count)")
    if !associatedProjects!.contains(reference) {
print("adding \(reference.recordID.recordName) regularly")
        associatedProjects!.append(reference)
        uploadToCloud(database)
    }
}

/**
 * An identifier used to store and recover conforming instances record.
 */
var recordID: CKRecordID { return CKRecordID(recordName: identifier) }

/**
 * This computed property generates a conforming instance's CKRecord (a key-value
 * cloud database entry). Any values that conforming instance needs stored should be
 * added to the record before returning from getter, and conversely should recover
 * in the setter.
 */
var record: CKRecord {
    get {
        let record = CKRecord(recordType: CrewParticipant.REC_TYPE, recordID: recordID)

        if let id = cloudIdentity { record[CrewParticipant.TOKEN] = id }

// There are several other records that are dealt with successfully here.

print("if let projects")
        // Referable properties
        if let projects = associatedProjects {
print("success: \(projects.count)")
            record[CrewParticipant.REFERENCES] = projects
        }

        return record
    }

    set { matchFromRecord(newValue) }
}

2 个答案:

答案 0 :(得分:9)

看起来你每次保存都会创建一个新的CKRecord。

CloudKit正在返回ServerRecordChanged,告诉您服务器上已存在具有相同recordID的记录,并且您的保存尝试被拒绝,因为服务器记录的版本不同。

  

每条记录都有一个更改标记,允许服务器跟踪保存该记录的时间。保存记录时,CloudKit会将记录本地副本中的更改标记与服务器上的更改标记进行比较。如果两个标记不匹配 - 意味着存在潜在冲突 - 服务器使用[ saveMolicy属性CKModifyRecordsOperation ]中的值来确定如何继续。

来源: CKModifyRecordsOperation Reference

虽然您使用的是CKDatabase.saveRecord便捷方法,但这仍然适用。默认的savePolicy为ifServerRecordUnchanged

首先,我建议转换到 CKModifyRecordsOperation ,尤其是在保存多条记录时。它使您可以更好地控制该过程。

其次,在保存对现有记录的更改时,您需要从服务器更改CKRecord。您可以通过以下任何方式完成此操作:

  1. 从CloudKit请求CKRecord,对该CKRecord进行更改,然后将其保存回CloudKit。
  2. 使用 CKRecord Reference 中的建议存储保存的CKRecord(保存后在完成块中返回的CKRecord),保留此数据,然后取消归档以获取CKRecord返回您可以修改并保存到服务器。 (这避免了一些网络往返请求服务器CKRecord。)
  3.   

    在本地存储记录

         

    如果将记录存储在本地数据库中,请使用encodeSystemFields(with :)方法对记录的元数据进行编码和存储。元数据包含记录ID和更改标记,稍后需要将本地数据库中的记录与CloudKit存储的记录同步。

    let record = ...
    
    // archive CKRecord to NSData
    let archivedData = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWithMutableData: archivedData)
    archiver.requiresSecureCoding = true
    record.encodeSystemFieldsWithCoder(archiver)
    archiver.finishEncoding()
    
    // unarchive CKRecord from NSData
    let unarchiver = NSKeyedUnarchiver(forReadingWithData: archivedData)  
    unarchiver.requiresSecureCoding = true 
    let unarchivedRecord = CKRecord(coder: unarchiver)
    

    来源: CloudKit Tips and Tricks - WWDC 2015

    请注意:如果其他设备在您请求/上次保存记录后保存对记录的更改,您仍会遇到ServerRecordChanged错误。存储了服务器记录。您需要通过获取最新的服务器记录来处理此错误,并将更改重新应用于该CKRecord。

答案 1 :(得分:1)

您可以使用 CKModifyRecordsOperation 的savePolicy绕过更改标记的跟踪

modifyRecordsOperation.savePolicy = .allKeys