【问题标题】:How do I prevent orphans when deleting a record from CloudKit?从 CloudKit 删除记录时如何防止孤儿?
【发布时间】:2018-07-26 20:06:46
【问题描述】:

CloudKit WWDC 视频建议像这样实现同步:

  • 跟踪本地更改
  • 将更改发送到服务器
  • 解决冲突
  • 使用 CKFetchRecordChangesOperation 获取服务器更改
  • 应用服务器更改
  • 保存服务器更改令牌

我在我的应用程序中遵循这种模式,但我遇到了删除和父子关系的问题。

假设我们有一个按类别划分的书籍列表。每本书都必须属于一个类别。

我从这样的数据开始:

SERVER
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Computer Programming: <empty>

如您所见,最终类别为空。假设我有两台设备具有这些数据的精确副本。

现在,在设备 1 上,用户将一本书 CloudKit Sync 添加到“计算机编程”:

DEVICE 1
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Computer Programming: "CloudKit Sync"

但是在设备 2 上,用户完全删除了“计算机编程”类别(它是空的,所以从设备 2 的角度来看这很好):

DEVICE 2
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"

设备 1 首先同步,因此它会创建一个新的 Book 条目,并将其 parent 字段设置为 Computer Programming

但现在设备 2 开始其同步过程。它将其更改应用于服务器,因此它删除了与“计算机编程”相对应的CKRecord。这与Device 2的世界观一致,类别为空,可以删除。

但是,当它从服务器中删除此类别时,这对于设备 1 的世界观和服务器本身来说是没有意义的。现在有一本名为 CloudKit Sync 的孤儿书,它有一个指向其父级的悬空指针。

如果我遵循 Apple 的 WWDC 建议,我该如何避免这种情况?根据同步的顺序,我可以很容易地达到不一致的状态,即孤儿书和无效的父参考。

我希望来自设备 2 的 Delete 命令返回一个错误,告诉我我要孤立一本书并阻止该操作发生,所以我可以采取一些措施来解决问题。

这可能吗?还有其他方法可以解决这个问题吗?

【问题讨论】:

    标签: ios synchronization icloud cloudkit


    【解决方案1】:

    是的,您希望设备 2 的行为是可能的。我看到 cloudkit 的三个方面将在您的场景中发挥作用。让我们先看看这些,然后看看它们如何在您的场景中使用。

    首先,假设两个(或所有)设备都订阅了对相应记录的更改,每个设备都会收到其他人添加或删除某些内容的通知。然后,接收警报的设备将有机会决定如何处理它。 (从本地视图中删除它,在服务器上替换它等)

    其次,您可以使用CKModifyRecordOperation 上的savePolicy 设置处理冲突的行为。您可以指定最后一次更改是否应覆盖旧记录、引发错误等。请参阅https://developer.apple.com/documentation/cloudkit/ckrecordsavepolicy?language=objc 了解这三个选项。 (我只在两个用户修改共同记录的情况下使用了这个,但是在另一个用户更新记录后删除应该会引发server record changed 错误)。

    第三,假设您已经配置了上述savePolicy,是服务器更改令牌本身。我发现最容易将更改令牌设想为最后修改的时间戳。 “我的这张记录的副本最后一次修改是在晚上 10:42”之类的。根据您在上述savePolicy 中选择的覆盖选项,设备将收到一个 NSError Server Record Changed 提醒您服务器上的版本是从晚上 10:56 开始的,并且您的本地版本可能不再有效。

    生成的 NSError 中的 userInfo 包括 3 个版本的相关记录:服务器上的当前版本、您尝试提交的版本和共同祖先版本。 Apple 的指南表示,由开发人员决定如何合并这些信息。但理论上,您可以区分更改,决定要保留哪些更改,然后提交新操作。

    关于您的具体情况:假设您完全授权并信任 dev1 和 dev2 删除记录,那么我将订阅创建和删除事件,并设置 savePolicy 以在尝试冲突更改时抛出错误。在这种情况下,设备 1 将添加记录,设备 2 将收到新记录的通知。如果设备 2 只是尝试删除旧记录,它应该会失败并出现 server record changed 错误,您可以将其显示为

    "别人修改了这条记录,你真的要删除吗 (是/否)。”

    设备 2 必须在继续之前刷新记录(并接收新的记录更改令牌)。之后,如果设备 2 仍想删除新记录,它可以,但随后设备 1 将通过上述订阅通知更改。然后,设备 1 会将新记录下载到其本地视图(或在这种情况下从中删除旧记录)。订阅通知可以提醒用户 1:

    “你的记录 Foo 刚刚被 Bar 删除”

    即使事件几乎同时发生,这也会起作用,因为其中一个更改将首先应用于服务器,而另一个设备的令牌将立即过期。因此,如果设备 2 设法先删除了记录,则设备 1 修改记录的尝试将失败并显示 server record changed,因为设备 1 的更改令牌现在已过期。设备 1 的错误处理程序必须根据您的业务规则决定是接受删除还是继续创建新记录。也许可以向用户 1 询问以下内容:

    “计算机编程”已从服务器中删除。你想重新创建 是吗?

    此时,用户 1 可以发送火焰电子邮件,要求其他用户停止删除他们新创建的记录,用户 2 可以要求人们停止重新创建他们刚刚“清理”的记录。 :)

    您可能会变得更复杂,也许让设备 1 优先于设备 2,这样当设备 1 收到记录被删除的通知时,设备 1 会将记录重新写入服务器。如果您有多个具有删除权限的用户,您可以确定优先顺序并构建适当的错误/通知处理程序。然而,这似乎非常复杂且容易出错。可能会发生自动响应(创建、删除、创建、删除、创建、删除)的循环。我仅将其作为假设示例,而不是推荐!

    最后,作为一个不同的例子,我的应用有不同的场景。我的记录是游戏会话。所有玩家都需要对会话数据的读取权限,但只有发起者可以选择完全删除记录。因此,您可以考虑是否真的授权多个用户删除共享记录。

    【讨论】: