如何(以及何时)在 CKRecord 上使用 iCloud 的 encodeSystemFields 方法?

How (and when) do I use iCloud's encodeSystemFields method on CKRecord?

encodeSystemFields 应该在我将记录保存在本地数据库中时使用。

导出该数据后,反序列化时是否必须执行任何特殊操作?

我应该根据该数据中的信息采取哪些行动?

作为变体(如果上一个问题未涵盖),此信息可帮助我防范什么? (我假设数据损坏)

encodeSystemFields 可用于避免必须再次从 CloudKit 获取 CKRecord 以更新它(除非记录冲突)。

想法是:

当您存储从 CloudKit 检索到的记录数据时(例如,通过 CKFetchRecordZoneChangesOperation 检索以将记录更改同步到本地存储):

1.) 将 CKRecord 存档到 NSData:

let record = ...

// archive CKRecord to NSData
let archivedData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWithMutableData: archivedData)
archiver.requiresSecureCoding = true
record.encodeSystemFieldsWithCoder(with: archiver)
archiver.finishEncoding()

2.) 将存档数据存储在本地(例如,在您的数据库中)与您的本地记录关联。

当您想将对本地记录所做的更改保存回 CloudKit:

1.) 从您存储的 NSData 中解压 CKRecord:

let archivedData = ... // TODO: retrieved from your local store

// unarchive CKRecord from NSData
let unarchiver = NSKeyedUnarchiver(forReadingWithData: archivedData)  
unarchiver.requiresSecureCoding = true 
let record = CKRecord(coder: unarchiver)

2.) 使用未存档的记录作为更改的基础。 (即在其上设置更改的值)

record["City"] = "newCity"

3.) 通过 CKModifyRecordsOperation 将记录保存到 CloudKit。


为什么?

来自苹果:

Storing Records Locally

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.

当您将更改保存到 CloudKit 中的 CKRecord 时,您需要将更改 保存到服务器的记录

您不能只创建一个具有相同 recordID 的新 CKRecord,在其上设置值,然后保存。如果这样做,您将收到“服务器记录已更改”错误 - 在这种情况下,这是因为现有服务器记录包含您的本地记录(从头开始创建)丢失的元数据。

所以你有两种选择来解决这个问题:

  1. 从 CloudKit 请求 CKRecord(使用 recordID),对该 CKRecord 进行更改,然后将其保存回 CloudKit。

  2. 使用 encodeSystemFields,并在本地存储元数据,将其解档以创建一个“基础”CKRecord,该 CKRecord 具有用于保存对所述 CKRecord 的更改的所有适当的元数据回到 CloudKit.

#2 节省您的网络往返次数*。

*假设另一台设备在此期间没有修改记录——这也是该数据帮助您防范的。如果另一台设备在您上次检索记录和您尝试保存记录之间修改了记录,CloudKit 将(默认情况下)拒绝您的记录保存尝试,并显示“服务器记录已更改”。这是您以适合您的应用程序和数据模型的方式执行冲突解决的线索。 (通常,通过从 CloudKit 获取新的服务器记录并在再次尝试保存之前将适当的值更改重新应用到该 CKRecord。)

注意: 任何时候你 save/retrieve 一个更新的 CKRecord to/from CloudKit,你必须记得更新你本地存储的归档 CKRecord .

从 iOS 15 / Swift 5.5 开始,此扩展程序可能会有所帮助:

public extension CKRecord {
    var systemFieldsData: Data {
        let archiver = NSKeyedArchiver(requiringSecureCoding: true)
        encodeSystemFields(with: archiver)
        archiver.finishEncoding()
        return archiver.encodedData
    }

    convenience init?(systemFieldsData: Data) {
        guard let una = try? NSKeyedUnarchiver(forReadingFrom: systemFieldsData) else {
            return nil
        }
        self.init(coder: una)
    }
}