【问题标题】:Storage options for offline and online iOS app with synchronization具有同步功能的离线和在线 iOS 应用程序的存储选项
【发布时间】:2020-05-09 16:14:51
【问题描述】:

让我们考虑这个示例用例,一个用于食谱的 iOS 应用:

  • 公共数据(由所有用户共享,只读,按需下载):当用户第一次打开应用程序时,他发现一个浅表(未完全下载)10 个食谱(从远程服务器获取)选项下载完整的食谱,然后他可以打开食谱的详细信息屏幕。并且在任何时候列表都可以增长,并且用户应该始终拥有最新的数据(从远程获取并保持同步)。这些数据应该可以离线使用,并且应该在在线时提取新内容。 (只读)

  • 私人数据(特定于用户):用户可以创建自定义配方,该配方存储在本地并远程同步。这些数据应该可以离线使用,并且在线时应该同步(读写)

  • 数据应在所有 iOS 设备中同步

我正在考虑使用 Core Data(离线)和 CloudKit(远程)。但我不确定这是否可以处理上述情况或是否有任何限制。您认为 Core Data(离线)和 CloudKit 是最佳选择吗?有什么限制吗?你还有什么推荐的选择

【问题讨论】:

  • 对于Firebase 来说,这听起来像是一份完美的工作。
  • 这几乎是我在我的应用程序中使用的确切设置(它也恰好是一个食谱应用程序)。我会写一篇文章来解决您的问题和疑虑,并尽快发布。 TL;DR 这个设置运行良好。
  • @richardpiazza 期待您的回答。食谱用作示例,但该应用程序适用于另一个领域。您能否在回答中说明您如何处理 Cloudkit 中的公共数据库未同步(不能离线使用??)这一事实
  • @zrzka 这是建议,但据我所知,Firebase 离线支持是有限的,我想要的是具有跨设备同步选项以及有新数据时的离线第一个应用程序

标签: ios swift core-data synchronization cloudkit


【解决方案1】:

您在问题中描述的架构是使用 Core Data 和 CloudKit 运行良好的架构。可能还有其他值得探索的选项(例如 Firebase、Realm),但由于您提到了 Apple 框架,我将回答这些问题。这都是我自己的经验,所以你的里程可能会有所不同


让我们从简化的答案开始。

Apple 提供NSPersistentCloudKitContainer,它封装了核心数据堆栈并将持久存储镜像到 CloudKit 私有数据库。尽管这对于新应用程序来说是一个很好的解决方案,但它不能与现有的 CloudKit 容器一起使用。 “Core Data 拥有从 Core Data 模型创建的 CloudKit 方案。现有的 CloudKit 容器与此模式不兼容。”但它仍然可能是一条值得探索的道路,并且有大量可用资源:


现在,让我们深入了解一些细节。

NSPersistentCloudKitContainer 之前,应用需要管理Core DataCloudKit 框架之间的同步和存储。尽管这可能看起来像是一个女儿的任务,但它是一个可以管理的任务。在接近解决方案时需要牢记以下几点:

  • 最好将您的 CloudKit 存储视为事实来源

  • 维护 Core Data 记录和 CloudKit 记录之间的唯一标识符是必不可少的。

  • 您的应用/用户体验主要应该来自 Core Data 存储。

  • 处理任务——比如创建配方、持久化配方、更新配方——因为Operations 可能是有益的。

 CloudKit

公共/私人/共享访问是 CloudKit 运作方式的核心。一个 CloudKit 容器具有三个数据库 (CKDatabase):

  • privateCloudDatabase:所有者可读,所有者可写。您无法通过 Developer Portal 看到。

  • publicCloudDatabase:世界可读,所有者可写。可以被角色锁定,并且可以通过开发者门户对您可见。

  • sharedCloudDatabase:共享参与者 (CKShare) 可用,您不可见。

这种结构非常符合您希望将公共数据和私有/用户数据分开的愿望。

可以在数据库上设置订阅 (CKSubscription) 以通知您已发生更改。此时,您可以让您的应用程序获取更改的记录。您可以提供一个更改令牌 (CKServerChangeToken),以将查询结果限制为仅显示自上次下载以来发生更改的记录。每个设备都有自己的订阅和令牌,因此它只会下载所需的数据并记录更改。通过这种方式,CloudKit 是事实的来源,所有的变化——不管它们发生在哪里——都会反映在其他设备上。

您不必等待通知发生即可查询更改。 CKFetchRecordZoneChangesOperation 将始终在执行竞争时提供新的更改令牌。因此,无论如何触发,检索更改都是相同的过程;通过通知或其他机制(如连接更改)或手动进行。

您可能希望为“公共”和“私有”数据库创建订阅,以确保可以监控两者的更改。

CloudKit 和核心数据架构

Core Data 和 CloudKit 之间的模型结构有一个重要区别,这以关系的形式出现。

Core Data 建议所有关系都是双向的。例如:Recipe 将与其Ingredients 有关系(一对多),而Ingredient 将与其Recipe 有关系(一对一)。

在 CloudKit 中,仅建议使用单向关系,使用 CKReference。因此,对于给出的示例,Recipe 不会直接引用成分,但 Ingredient 将具有对使用它的 Recipe 的关系引用。

作为 Core Data 和 CloudKit 之间互操作的一部分,您需要一个在两个环境之间唯一且可查询的标识符。实现此目的的常用方法是使用UUIDCKRecord.ID 由一个“recordName”(字符串)和一个“zoneID”(CKRecordZone.ID)组成。提供 UUID.uuidString 作为“recordName”将在 CloudKit 中创建唯一记录,但也会为您提供对 Core Data 中实体的一致引用。

核心数据

正如您所指出的,离线访问数据对应用的可用性至关重要。 Core Data 在这里是一个不错的选择。即使您可以直接从CKQuery 显示结果,但保留该数据确实有一些优势。我将本地核心数据存储视为我的应用程序中所有数据的接口。这意味着我的用户数据只有一个接口。

对本地数据所做的任何更改也会发送到 CloudKit,来自 CloudKit 的任何记录都会在本地核心数据存储中添加或修改。

以这种方式使用 Core Data 可以让您利用 NSFetchedResultsController 等工具,这些工具可用于自动反映对一组实体所做的更改。

此外,您还可以连接到 Notification NSManagedObjectContextDidSave,在从 CloudKit 交互合并更改时帮助您的应用 UI 保持最新。

请记住,尝试使用 NSPersistentContainer.viewContext 执行大量任务可能会导致性能问题,因此您需要熟悉 DispatchQueues。

操作

CloudKit 交互是基于操作的。查询和写入被定义为Operation 的子类。定义每个任务,然后将其添加到要处理的队列中。这些操作是异步执行的。我发现将这种编程风格扩展到应用程序的其他区域很有用。

例如:当我创建一个新的Recipe 时,即针对Core Data 的Operation,然后我有一个Operation 在CloudKit 中创建该记录。第二个操作依赖于第一个,如果第一个不成功则取消。

将 Core Data 和 CloudKit 之间的协调视为一系列需要执行的任务变得容易一些。更改通知会发送到您的应用 > 获取更改(使用更改令牌)> 在 Core Data > 更新 UI(自动/手动)中添加/更新这些记录。每一个都是链条中的一个环节,并依赖于最后一个要完成的工作,然后再继续前进。

您可以使用基础OperationOperationQueueProcedureKit 之类的框架来帮助完成此过程。


总的来说,Core Data 和 CloudKit 很好地集成在一起,并且通常用于您想要实现的场景类型。根据您所处的开发复杂性或状态,NSPersistentCloudKitContainer 可能是一个很好的起点。但是,一旦您超越了它的功能,您就会想要了解上面指出的挑战和功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-16
    • 1970-01-01
    • 2022-11-16
    • 2017-05-21
    • 2010-10-14
    • 1970-01-01
    • 2013-08-08
    相关资源
    最近更新 更多