【问题标题】:Why relationship must be optional when using Core Data with CloudKit?为什么将 Core Data 与 CloudKit 一起使用时关系必须是可选的?
【发布时间】:2021-05-28 12:21:34
【问题描述】:

以下是 Apple 的 doc 中将 Core Data 与 Cloudkit 一起使用的要求之一:

所有关系都必须是可选的。由于操作规模的限制, 关系更改可能不会自动保存。

尝试使用与 CloudKit 的可选关系会导致错误:

线程 1:致命错误:未解决的错误 Error Domain=NSCocoaErrorDomain Code=134060 “发生核心数据错误。” UserInfo={NSLocalizedFailureReason=CloudKit 集成要求所有关系都是可选的,以下不是: Some_Managed_Object: some_attribute}, ["NSLocalizedFailureReason": CloudKit 集成要求所有关系都是可选的,以下不是: Some_Managed_Object: some_attribute]

我想知道,这不是完全违背了使用关系的目的吗?

例如,假设我有两个实体:AccountTransfer。由于转账始终与源账户和目标账户相关联,Transfer 应该与Account 有两个非可选关系。但由于上述要求,这些关系必须是可选的

文档给出了解释:“(这是因为)关系更改可能无法自动保存”。这似乎表明,在 Cloudkit 和 Core Data 之间同步期间,关系可能是不完整的并且不完整的关系会暴露给 App 代码。这对我来说似乎是一个严重的问题,因为:

  1. 在我上面的例子中,这两个关系本质上是非可选的。将它们更改为可选会使模态变得毫无意义。

  2. 即使在那些关系应该是可选的示例中,虽然不完整的关系在语法上是正确的,但它可能会导致意外的不一致问题。

所以我想知道这应该如何在实际应用中工作?这对我来说似乎很破碎。我是不是误会了什么? 难道使用 Cloudkit 同步 Core Data 只适用于一小部分只使用可选关系的应用程序?(如果是这样,我想知道其他 Core Data 应用程序是如何在设备之间同步数据的。 )


在相关说明中:像许多其他人一样,我努力搜索有关 Cloudkit 和 Core Data 使用的同步和冲突解决算法的详细信息。我能找到的仅有的几条信息是:

在最终一致的分布式系统中,您永远无法“知道” 您在云中拥有现有数据或设备。你的申请 将简单地“在某个时候发现”这些数据存在并且需要 旨在处理该问题

是的,Core Data CloudKit 使用 CRDT 实现了对多关系!

冲突解决由自动执行 使用最后一个写入器的 NSPersistentCloudKitContainer 赢得合并策略。

虽然我大致了解这些信息中的每一条,但它们并没有直接得出以下结论:1) Cloudkit 和 Core Data 之间的数据更改是否以原子方式同步?更重要的是 2) 在同步期间是否会将不完整的数据暴露给 App 代码?

我的猜测是 1) 不,2) 是。但是,如果在同步期间不完整的数据更改暴露给 App 代码,我很难理解如何编写真正的应用程序。 难道要使用 Cloudkit 来同步 Core Data,必须将模式设计为​​在不完整的关系下正常工作?

如果有人能分享你的理解,我将不胜感激。

【问题讨论】:

    标签: ios core-data cloudkit


    【解决方案1】:

    越想越相信:

    1. 数据更改以非原子方式在 Cloudkit 和 Core Data 之间同步。

    2. 数据同步期间的不完整状态会暴露给应用代码。

    3. 这些行为是由于同步的执行方式造成的,几乎无法解决。

    因此 Cloudkit 对 Core Data 的内置同步支持仅对一小部分不需要数据完整性的简单应用有用。

    对于严肃的应用程序,需要考虑通过直接使用 Cloudkit 来实现一种自定义方法。但是编写自己的同步算法并不是一件容易的事,而且充满了陷阱。

    【讨论】:

      【解决方案2】:

      我也为此苦苦挣扎,并提出了一些解决方案。

      1. 不要使用关系并保持模型肤浅(不理想或可扩展)

      由于显而易见的原因,这不是理想的或可扩展的,但在我的一个应用程序中,我将 PKDrawing 数据直接存储在带有其他绘图相关内容的事件实体上,而不是使用关系。不过,这确实是在与 CoreData 框架作斗争,而且是糟糕的设计。


      1. 在提取期间存在检查关系。

      这可能是用户创建数据的最佳解决方案。假设您有一个带有一对一画布的草图。 在获取要在列表中显示的草图时,仅获取具有非零画布关系的草图。

      Example of checking relationship


      1. 提供默认值

      这适用于非用户创建的东西。例如在上面的示例中,Canvas 也可以与 PaperTemplate 具有一对一的关系。 PaperTemplate 存储诸如 PaperStyle (grid, lined) 之类的东西。由于这些数据可以很容易地在 PaperSettingsView 中重新创建(通过选择器),如果关系为零,我们可以简单地恢复为 awakeFromFetch 中的 DefaultValue。注意:我不确定,但这可能会导致孤立的 PaperTemplate 实体。


      最终,我认为解决方案 #2 是最好的全方位解决方案。如果我们只获取具有非零关系的对象,我们可以确保模型是正确的。因此,您只能使用非零源帐户和目标帐户获取转移。如果这是使用 NSFetchedResultsController 或 SwiftUI @FetchRequest 完成的,您的视图可以在对象变为“有效”时保持同步。虽然保存可能不是原子性的,但客户端可以通过忽略不完整的对象来决定如何使用更改和模仿原子性。

      编辑:

      虽然我认为这样做是在对抗 Core Data。您可以使用手动编码/解码的 Transformable 或 Codable 结构来存储 blob。确保选中“允许外部存储”。

      所以你可以使用:

      class Transfer: NSManagedObject {
         var sourceAccountData: Data?
         var destinationAccountData: Data?
      }
      
      // Could use class and NSSecureCoding instead if you wanted, but I like structs.
      struct Account: Codable {
      }
      

      【讨论】:

      • 感谢您的信息。但是,您的解决方案 #2 和 #3 对于需要数据完整性的应用程序(例如金融应用程序)是不可行的。
      • @rayx 我同意这很糟糕。 #1实际上是我似乎使用最多的方法,但确实感觉不对。当我需要数据存在时,我发现自己存储 blob。我将添加一个包含更多详细信息的编辑。
      • 谢谢。我明白你的意思了。您将关系中涉及的所有实体打包在一个条目(一个 blob)中。
      【解决方案3】:

      我的两分钱,解决方案二是正确的解决方案,无论如何都应该为任何强大的应用程序完成。您应该始终验证数据!想象一下,您从服务器从 json 下载数据,您不会在导入之前验证数据是否正确吗?这是同一件事,只是有点不同。

      【讨论】:

      猜你喜欢
      • 2020-03-14
      • 2020-09-17
      • 2019-06-02
      • 1970-01-01
      • 2011-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      相关资源
      最近更新 更多