Что произойдет, если приложение будет убито при использовании CKFetchRecordZoneChangesOperation? - PullRequest
0 голосов
/ 15 мая 2018

Отображение, что вы используете CKFetchRecordZoneChangesOperation для получения любых изменений.

Вы успешно загрузили все измененные CKRecords, однако на данный момент вы еще не создали локальный кеш. Но вы кэшировали новый токен изменения в recordZoneChangeTokensUpdatedBlock

Вы собираетесь создать локальный кеш для CKRecords, но каким-то образом ваш пользователь решил убить ваше приложение, и вы закрыты.

После перезапуска вы хотите повторно загрузить изменения, но теперь у вас есть НОВЫЙ токен изменений, начиная с Нового токена, изменений не было.

Как это решить?

======= * ========

Кто-то указал, что я могу кэшировать токен после записи записей в локальную базу данных.

Но это не всегда возможно, потому что при первом запуске приложения приложению может потребоваться загрузить много CKRecords, которые потребляют память, если вы не обрабатываете их вовремя, с другой стороны, CKFetchRecordZoneChangesOperation использует ДВУХ разных блок, один для новых записей, полученных с сервера, другой для обновления токена. Поэтому вам придется написать сложный код для координации ДВУХ БЛОКОВ.

var recordChangedBlock

var recordZoneChangeTokensUpdatedBlock

1 Ответ

0 голосов
/ 16 мая 2018

Только не кэшируйте свой токен до тех пор, пока после не обновите локальный кеш записей.Работает для меня ...

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:

Только что увидел последнюю частьтвой вопрос выше.Если ваше приложение зависит от удаленных данных, чтобы их можно было использовать, похоже, у вас есть только несколько вариантов.Дайте какой-то индикатор прогресса, который они должны ждать, прежде чем использовать приложение.Или реструктурируйте то, что у вас есть, чтобы вы могли использовать приложение ДО того, как у вас будут эти удаленные данные.Возможно, он не находится в конечном состоянии, но, по крайней мере, его можно будет использовать.

Для того, над чем я работаю, потенциально может быть десятки или сотни тысяч записей в начальной синхронизации, и было бы нереально ждать, пока все они синхронизируются, прежде чем использовать приложение.Они могут начать использовать приложение, и синхронизация может произойти, КАК они используют и изменяют базовые данные, потому что они созданы для обеспечения устойчивости к этому.

...