【问题标题】:What happens if app is killed when using CKFetchRecordZoneChangesOperation?如果应用在使用 CKFetchRecordZoneChangesOperation 时被杀死会发生什么?
【发布时间】:2018-05-15 15:47:04
【问题描述】:

假设您正在使用 CKFetchRecordZoneChangesOperation 获取任何更改。

您已成功获取所有更改的CKRecords,但是,您目前尚未创建本地缓存。但是您确实在recordZoneChangeTokensUpdatedBlock 中缓存了一个新的更改令牌 您即将为 CKRecords 创建一个本地缓存,但不知何故您的用户决定终止您的应用程序,而您被终止了。

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

如何解决?


有人指出我可以在Records写入本地数据库后缓存token。

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

var recordChangedBlock
var recordZoneChangeTokensUpdatedBlock

【问题讨论】:

  • 我想要一个标准的解决方案,也就是说,不是一些 hack。
  • @TomHarrington 有什么想法吗?

标签: ios swift core-data cloudkit


【解决方案1】:

在您更新本地记录缓存之前不要缓存您的更改令牌。对我有用...

要添加的 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:

刚刚看到你上面问题的最后一部分。如果您的应用程序依赖于远程数据才能使用,那么您似乎只有几个选择。提供某种进度指示器,让他们在使用该应用程序之前必须等待。或者重新构建你所拥有的,以便你可以在拥有远程数据之前使用该应用程序。它可能不是最终状态,但至少可以使用。

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

【讨论】:

  • 哎呀,这意味着作为评论,而不是答案
  • 评论或回答,我相信你是对的,先生;-)
  • @BrianM 但这是不可能的。新记录从块以外的不同块返回以接收更新的令牌。你必须编写很多复杂的代码来协调它们
  • @BrianM 在新设备上安装应用程序时,应用程序必须下载大量的CKRecords,如果您不及时处理会消耗大量RAM。我等不及要下载所有 CKRecords 然后将它们写入本地数据库。
  • @BrianM 您可以尝试编写一些代码并将其粘贴到此处。我真的不知道该怎么做。
【解决方案2】:

使用长寿命操作。 https://developer.apple.com/documentation/cloudkit/ckoperation

        zoneOperation.configuration.isLongLived = true

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多