具有同步功能的离线和在线 iOS 应用程序的存储选项

iOS*_*eek 1 synchronization core-data ios swift cloudkit

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

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

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

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

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

ric*_*zza 5

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

\n
\n

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

\n

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

\n\n
\n

现在,让我们深入研究一些细节。

\n

在此之前NSPersistentCloudKitContainer,应用程序需要管理Core DataCloudKit框架之间的同步和存储。尽管这看起来像是一项艰巨的任务,但它是一项易于管理的任务。当您寻求解决方案时,需要记住以下几点:

\n
    \n
  • 最好将您的 CloudKit 商店视为事实来源

    \n
  • \n
  • 维护核心数据记录和 CloudKit 记录之间的唯一标识符至关重要。

    \n
  • \n
  • 您的应用程序/用户体验主要应该由核心数据存储驱动。

    \n
  • \n
  • 处理任务——比如创建菜谱、保存菜谱、更新菜谱——可能Operation是有益的。

    \n
  • \n
\n

\xc2\xa0CloudKit

\n

公共/私有/共享访问是 CloudKit 功能的核心。CloudKit 容器具有三个数据库 ( CKDatabase):

\n
    \n
  • privateCloudDatabase:所有者可读,所有者可写。您无法通过开发者门户看到。

    \n
  • \n
  • publicCloudDatabase:世界可读,所有者可写。可以按角色锁定,并且可以通过开发者门户查看。

    \n
  • \n
  • sharedCloudDatabase:可供共享参与者 ( CKShare) 使用,但您不可见。

    \n
  • \n
\n

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

\n

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

\n

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

\n

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

\n

CloudKit 和核心数据架构

\n

Core Data 和 CloudKit 之间的模型构建方式存在重要差异,这以关系的形式出现。

\n

Core Data 建议所有关系都是双向的。例如: ARecipe与其 s 存在关系Ingredient(一对多),并且 anIngredient与其 s 存在关系Recipe(一对一)。

\n

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

\n

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

\n

核心数据

\n

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

\n

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

\n

以这种方式使用核心数据可以让您利用诸如NSFetchedResultsController可用于自动反映对一组实体所做的更改的工具。

\n

此外,您还可以挂钩Notification NSManagedObjectContextDidSavedo 帮助在从 CloudKit 交互合并更改时帮助您的应用程序 UI 保持最新状态。

\n

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

\n

运营

\n

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

\n

例如:当我创建一个新的Recipe,这是一个Operation针对核心数据,然后我有一个Operation在 CloudKit 中创建该记录。第二个操作依赖于第一个操作,如果第一个操作不成功,则第二个操作将被取消。

\n

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

\n

您可以使用基础Operation和,或像ProcedureKitOperationQueue这样的框架来帮助完成此过程。

\n
\n

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

\n