在您更新本地记录缓存之前不要缓存您的更改令牌。对我有用...
要添加的 EDII:
让 CloudKit 同步是一件复杂的事情——至少对我来说是这样!我是 iOS 新手,我花了很长时间才让我的系统正常工作 - 通过学习操作和 GCD 绕道而行。您需要将其拆分为不同的操作或方法才能成功完成。就像您所说的那样,同步可以随时中断/取消,您的系统需要对此具有弹性。
我建议不要使用 CKFetchRecordZoneChangesOperation 来处理任何东西,只是用来获取这些结果和令牌,然后将它们传递到系统中的下一步。我正在使用操作,并且我对 CKFetchRecordZoneChangesOperation 有一个包装操作——它是获取、修改和上传更改链中的一个步骤。这不是整个课程,只是适用于这个问题的相关 sn-ps 让您了解我的意思。您的代码将(可能非常)不同:
class FetchRecordZoneChangesOperation: AsyncOperation {
// MARK: - Properties
var inputRecordZoneIDs: [CKRecordZoneID]?
var outputCKRecords: [CKRecord]?
var outputDeletedRecordIDs: [CKRecordID]?
var outputServerChangeToken: CKServerChangeToken?
var useServerChangeToken = true
override func main() {
if self.isCancelled {
self.finish()
return
}
if let recordZoneIDsDependency = dependencies
.filter({ $0 is CKRecordZoneIDsProvider })
.first as? CKRecordZoneIDsProvider
, inputRecordZoneIDs == nil {
inputRecordZoneIDs = recordZoneIDsDependency.ckRecordZoneIDs
}
// This record zone stuff is kinda redundant but it works...
var recordZoneID: CKRecordZoneID
var recordZoneIDs: [CKRecordZoneID]
if let zoneIDs = self.inputRecordZoneIDs, let zoneID = zoneIDs.first {
recordZoneID = zoneID
recordZoneIDs = zoneIDs
} else {
recordZoneID = UserDefaults.standard.ckCurrentRecordZoneID
recordZoneIDs = [UserDefaults.standard.ckCurrentRecordZoneID]
}
let operation = CKFetchRecordZoneChangesOperation()
// QOS
operation.qualityOfService = .userInitiated
operation.recordZoneIDs = recordZoneIDs
// if I have a database change token, use that, otherwise I'm getting all records
if useServerChangeToken, let token = UserDefaults.standard.ckRecordZoneChangeToken {
// TODO: This will change when I have more than 1 list
let fetchOptions = CKFetchRecordZoneChangesOptions()
fetchOptions.previousServerChangeToken = token
operation.optionsByRecordZoneID = [ recordZoneID : fetchOptions]
}
operation.recordChangedBlock = { record in
if self.outputCKRecords != nil {
self.outputCKRecords?.append(record)
} else {
self.outputCKRecords = [record]
}
}
operation.recordWithIDWasDeletedBlock = { recordID, somethingStringy in
if self.outputDeletedRecordIDs != nil {
self.outputDeletedRecordIDs?.append(recordID)
} else {
self.outputDeletedRecordIDs = [recordID]
}
}
operation.recordZoneChangeTokensUpdatedBlock = { recordZoneID, serverChangeToken, clientChangeTokenData in
self.outputServerChangeToken = serverChangeToken
}
operation.recordZoneFetchCompletionBlock = { recordZoneID, serverChangeToken, clientChangeTokenData, moreComing, error in
if error != nil {
cloudKit.errorController.handle(error: error, operation: .fetchChanges)
} else {
// Do I need to handle things with the clientChangeTokenData? Working flawlessly without currently. Right now I just store the server one
self.outputServerChangeToken = serverChangeToken
}
}
}
然后接下来的操作将其拾取到本地修改记录:
class ModifyObjectsOperation: AsyncOperation {
var debug = true
var debugMore = false
var inputCKRecords: [CKRecord]?
var inputDeleteIDs: [CKRecordID]?
var inputRecordZoneChangeToken: CKServerChangeToken?
var inputDatabaseChangeToken: CKServerChangeToken?
override func main() {
if isCancelled {
self.finish()
return
}
if let recordDependency = dependencies
.filter({ $0 is CKRecordsProvider })
.first as? CKRecordsProvider
, inputCKRecords == nil {
inputCKRecords = recordDependency.ckRecords
}
if let recordZoneTokenDependency = dependencies
.filter({ $0 is CKRecordZoneTokenProvider })
.first as? CKRecordZoneTokenProvider
, inputRecordZoneChangeToken == nil {
inputRecordZoneChangeToken = recordZoneTokenDependency.ckRecordZoneChangeToken
}
if let databaseTokenDependency = dependencies
.filter({ $0 is CKDatabaseTokenProvider })
.first as? CKDatabaseTokenProvider
, inputDatabaseChangeToken == nil {
inputDatabaseChangeToken = databaseTokenDependency.ckDatabaseChangeToken
}
if let deleteDependency = dependencies
.filter({ $0 is CKRecordIDsProvider })
.first as? CKRecordIDsProvider
, inputDeleteIDs == nil {
inputDeleteIDs = deleteDependency.ckRecordIDs
}
if self.inputCKRecords == nil && self.inputDeleteIDs == nil {
if self.debug {
print("? ModifyObjectsOperation - no changes or deletes ??")
}
self.finish()
return
}
// NOW MODIFY YOUR RECORDS HERE. IF SUCCESSFUL, CACHE YOUR TOKEN.
}
如果您有大量数据要同步或有大量记录,那么您是对的,这需要多次传输。上传比下载更痛苦...有 200 条记录的上传限制(尽管我发现这并没有严格执行 - 我一次成功上传了多达 400 条小记录)。我发现我可以在一个块中下载数千条小记录。
真正知道您是否安全的唯一方法是等到您进行更改,在本地缓存您的更改,然后才保存该 changeToken。您宁愿获得重复的数据,也不愿在此过程中丢失某些东西。如果你做对了,你永远不会失去一些东西,你最终可能会做一些多余的工作。
编辑 2:
刚刚看到你上面问题的最后一部分。如果您的应用程序依赖于远程数据才能使用,那么您似乎只有几个选择。提供某种进度指示器,让他们在使用该应用程序之前必须等待。或者重新构建你所拥有的,以便你可以在拥有远程数据之前使用该应用程序。它可能不是最终状态,但至少可以使用。
对于我正在处理的工作,初始同步时可能会有数万或数十万条记录,并且在使用应用程序之前等待所有这些记录同步是不可行的。他们可以开始使用该应用程序,并且可以在他们使用和更改基础数据时进行同步,因为它的构建是为了适应这种情况。