使用CKFetchRecordZoneChangesOperation进行成像以获取任何更改。
您已成功获取所有已更改的CKRecords,但目前尚未创建本地缓存。但是你确实在recordZoneChangeTokensUpdatedBlock
您即将为CKRecords制作本地缓存,但不知何故,您的用户决定终止您的应用,并且您已被终止。
重新启动后,您想要重新下载更改,但现在有了新的更改令牌,从新令牌开始,没有进行任何更改。
如何解决这个问题?
======= * ======
有人指出我可以在将记录写入本地数据库后缓存令牌。
但这并不总是可行的,因为在应用程序的首次启动时,如果您不及时处理它们,应用程序可能必须下载大量消耗内存的CKRecords,另一方面,CKFetchRecordZoneChangesOperation使用两个不同的块,一个用于从服务器获取的新记录,另一个用于令牌更新。所以你必须编写复杂的代码来协调两个块。
var recordChangedBlock
var recordZoneChangeTokensUpdatedBlock
答案 0 :(得分:2)
在之后更新本地记录缓存之前,请不要缓存您的更改令牌。对我有用......
EDII TO ADD:
获取CloudKit同步是一个复杂的野兽 - 至少对我来说是这样!我是iOS的新手,我花了很长时间才使我的系统正常工作 - 通过学习操作和GCD而走弯路。您需要将其拆分为不同的操作或方法才能成功完成。就像你说的那样,可以随时中断/取消同步,你的系统需要具备弹性。
我建议不要使用CKFetchRecordZoneChangesOperation处理任何事情,只是用来获取那些结果和标记,然后将它们传递给系统中的下一步。我正在使用操作,并且我有一个CKFetchRecordZoneChangesOperation的包装操作 - 它是链中的一个步骤,用于获取,修改和上传更改。这不是全班,只是适用于这个问题的相关片段,让您了解我的意思。您的代码将(可能非常)不同:
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:
刚看到上面问题的最后一部分。如果您的应用依赖于远程数据甚至可用,您似乎只有几个选项。给出一些他们在使用应用程序之前必须等待的进度指示器。或者重新架构您已经拥有的内容,以便在拥有远程数据之前可以使用该应用程序。它可能不是最终状态,但至少它可以使用。
对于我正在进行的工作,初始同步可能会有数十或数十万条记录,在使用应用程序之前等待所有记录同步是不可行的。他们可以开始使用该应用程序,并且可以在他们使用和更改基础数据时进行同步,因为它已经构建为具有弹性。