Только не кэшируйте свой токен до тех пор, пока после не обновите локальный кеш записей.Работает для меня ...
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:
Только что увидел последнюю частьтвой вопрос выше.Если ваше приложение зависит от удаленных данных, чтобы их можно было использовать, похоже, у вас есть только несколько вариантов.Дайте какой-то индикатор прогресса, который они должны ждать, прежде чем использовать приложение.Или реструктурируйте то, что у вас есть, чтобы вы могли использовать приложение ДО того, как у вас будут эти удаленные данные.Возможно, он не находится в конечном состоянии, но, по крайней мере, его можно будет использовать.
Для того, над чем я работаю, потенциально может быть десятки или сотни тысяч записей в начальной синхронизации, и было бы нереально ждать, пока все они синхронизируются, прежде чем использовать приложение.Они могут начать использовать приложение, и синхронизация может произойти, КАК они используют и изменяют базовые данные, потому что они созданы для обеспечения устойчивости к этому.