Swift/CloudKit:记录更改后,上传触发器会“更改服务记录" [英] Swift / CloudKit: After record changed, upload triggers "Service Record Changed"

查看:50
本文介绍了Swift/CloudKit:记录更改后,上传触发器会“更改服务记录"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试向云工具包中的记录添加CKReference,但是尝试不断触发服务记录已更改".从控制台消息中显示了我的println(控制台消息和下面的代码)),我正在上传具有0个引用的记录,然后在附加引用时,我看到尝试上传具有1个引用的记录.然后我得到了错误.

I'm trying to add a CKReference to a record in cloud kit, but the attempt keeps triggering "Service Record Changed". From the console messages my println's have shown (console messages and code below), I'm uploading said record with 0 references, then when I attach the reference I'm seeing an attempt to upload the record with 1 reference. Then I'm getting the error.

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

As I understand it, "Service Record Changed" shouldn't be triggered because the values in the Reference List have changed (the record has an entire extra field). Even though I'm in development mode, I manually created key-value field for the Reference List, because the first record upload doesn't include the field when the reference list is empty (uploading an empty array causes another error).

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

I'll include code in order of relevance (you'll be able to see most of the println's) after the console messages. The whole project is on github and I can link to it or include more code if needed.

相关控制台:

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 

来自CrewParticipant的代码:

Code from CrewParticipant:

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

发生上载的通用代码(适用于其他几个类)

generic code (which works for several other classes) where upload occurs:

/**
 * This method uploads any instance that conforms to recordable up to the cloud. Does not check any 
 * redundancies or for any constraints before (over)writing.
 */
func uploadRecordable<T: Recordable>
    (instanceConformingToRecordable: T, database: CKDatabase, completionHandler: (() -> ())? = nil) {
print("uploading \(instanceConformingToRecordable.recordID.recordName)")
if let referable = instanceConformingToRecordable as? Referable { print("with \(referable.references.count) references") }
    database.saveRecord(instanceConformingToRecordable.record) { record, error in
        guard error == nil else {
print("u!error for \(instanceConformingToRecordable.recordID.recordName)")
            self.tempHandler = { self.uploadRecordable(instanceConformingToRecordable,
                                                       database: database,
                                                       completionHandler: completionHandler) }
            CloudErrorHandling.handleError(error!, errorMethodSelector: #selector(self.runTempHandler))
            return
        }
print("upload succeeded: \(record!.recordID.recordName)")
        if let handler = completionHandler { handler() }
    }
}

/**
 * This method comprehensiviley handles any cloud errors that could occur while in operation.
 *
 * error: NSError, not optional to force check for nil / check for success before calling method.
 *
 * errorMethodSelector: Selector that points to the func calling method in case a retry attempt is
 * warranted. If left nil, no retries will be attempted, regardless of error type.
 */
static func handleError(error: NSError, errorMethodSelector: Selector? = nil) {

    if let code: CKErrorCode = CKErrorCode(rawValue: error.code) {
        switch code {

        // This case requires a message to USER (with USER action to resolve), and retry attempt.
        case .NotAuthenticated:
            dealWithAuthenticationError(error, errorMethodSelector: errorMethodSelector)

        // These cases require retry attempts, but without error messages or USER actions.
        case .NetworkUnavailable, .NetworkFailure, .ServiceUnavailable, .RequestRateLimited, .ZoneBusy, .ResultsTruncated:
            guard errorMethodSelector != nil else { print("Error Retry CANCELED: no selector"); return }
            retryAfterError(error, selector: errorMethodSelector!)

        // These cases require no message to USER or retry attempts.
        default:
            print("CKError: \(error)")
        }            
    }
}

推荐答案

似乎您每次保存都在创建一个新的CKRecord.

It looks like you are creating a new CKRecord every time you save.

CloudKit返回了 ServerRecordChanged ,告诉您服务器上已经存在具有相同recordID的记录,并且由于服务器记录的版本不同,您的保存尝试被拒绝.

CloudKit is returning ServerRecordChanged to tell you that a record with the same recordID already exists on the server, and your attempt to save was rejected because the server record's version was different.

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

Each record has a change tag that allows the server to track when that record was saved. When you save a record, CloudKit compares the change tag in your local copy of the record with the one on the server. If the two tags do not match—meaning that there is a potential conflict—the server uses the value in the [savePolicy property of CKModifyRecordsOperation] to determine how to proceed.

来源: CKModifyRecordsOperation参考

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

Although you are using the CKDatabase.saveRecord convenience method, this still applies. The default savePolicy is ifServerRecordUnchanged.

首先,我建议过渡到 CKModifyRecordsOperation ,尤其是在保存多个记录的情况下.它使您可以更好地控制该过程.

First, I would suggest transitioning to CKModifyRecordsOperation, especially if you are saving multiple records. It gives you much more control over the process.

第二,将更改保存到现有记录时,需要从服务器对CKRecord进行更改.您可以通过以下任意一种方式完成此操作:

Second, you need to make changes to the CKRecord from the server, when saving changes to an existing record. You can accomplish this by any of the following:

  1. 从CloudKit请求CKRecord,对该CKRecord进行更改,然后将其保存回CloudKit.
  2. 使用 CKRecord参考 ,将其保留下来,然后将其取消存档以获取可以修改并保存到服务器的CKRecord.(这避免了一些网络往返请求服务器CKRecord.)

本地存储记录

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

If you store records in a local database, use the encodeSystemFields(with:) method to encode and store the record’s metadata. The metadata contains the record ID and change tag which is needed later to sync records in a local database with those stored by 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技巧和窍门-WWDC 2015

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

Keep in mind: you can still encounter the ServerRecordChanged error if another device saves a change to the record after you requested it / last saved it & stored the server record. You need to handle this error by getting the latest server record, and re-applying your changes to that CKRecord.

这篇关于Swift/CloudKit:记录更改后,上传触发器会“更改服务记录"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆