如果app在使用CKFetchRecordZoneChangesOperation时被杀死会发生什么?

时间:2018-05-15 15:47:04

标签: ios swift core-data cloudkit

使用CKFetchRecordZoneChangesOperation进行成像以获取任何更改。

您已成功获取所有已更改的CKRecords,但目前尚未创建本地缓存。但是你确实在recordZoneChangeTokensUpdatedBlock

中缓存了一个新的更改令牌

您即将为CKRecords制作本地缓存,但不知何故,您的用户决定终止您的应用,并且您已被终止。

重新启动后,您想要重新下载更改,但现在有了新的更改令牌,从新令牌开始,没有进行任何更改。

如何解决这个问题?

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

有人指出我可以在将记录写入本地数据库后缓存令牌。

但这并不总是可行的,因为在应用程序的首次启动时,如果您不及时处理它们,应用程序可能必须下载大量消耗内存的CKRecords,另一方面,CKFetchRecordZoneChangesOperation使用两个不同的块,一个用于从服务器获取的新记录,另一个用于令牌更新。所以你必须编写复杂的代码来协调两个块。

var recordChangedBlock

var recordZoneChangeTokensUpdatedBlock

1 个答案:

答案 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:

刚看到上面问题的最后一部分。如果您的应用依赖于远程数据甚至可用,您似乎只有几个选项。给出一些他们在使用应用程序之前必须等待的进度指示器。或者重新架构您已经拥有的内容,以便在拥有远程数据之前可以使用该应用程序。它可能不是最终状态,但至少它可以使用。

对于我正在进行的工作,初始同步可能会有数十或数十万条记录,在使用应用程序之前等待所有记录同步是不可行的。他们可以开始使用该应用程序,并且可以在他们使用和更改基础数据时进行同步,因为它已经构建为具有弹性。