数据持久化方案解析(十七) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(一)

版本记录

版本号 时间
V1.0 2020.11.11 星期三

前言

数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说plist文件(属性列表)、preference(偏好设置)、NSKeyedArchiver(归档)、SQLite 3CoreData,这里基本上我们都用过。这几种方案各有优缺点,其中,CoreData是苹果极力推荐我们使用的一种方式,我已经将它分离出去一个专题进行说明讲解。这个专题主要就是针对另外几种数据持久化存储方案而设立。
1. 数据持久化方案解析(一) —— 一个简单的基于SQLite持久化方案示例(一)
2. 数据持久化方案解析(二) —— 一个简单的基于SQLite持久化方案示例(二)
3. 数据持久化方案解析(三) —— 基于NSCoding的持久化存储(一)
4. 数据持久化方案解析(四) —— 基于NSCoding的持久化存储(二)
5. 数据持久化方案解析(五) —— 基于Realm的持久化存储(一)
6. 数据持久化方案解析(六) —— 基于Realm的持久化存储(二)
7. 数据持久化方案解析(七) —— 基于Realm的持久化存储(三)
8. 数据持久化方案解析(八) —— UIDocument的数据存储(一)
9. 数据持久化方案解析(九) —— UIDocument的数据存储(二)
10. 数据持久化方案解析(十) —— UIDocument的数据存储(三)
11. 数据持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的数据存储示例(一)
12. 数据持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的数据存储示例(二)
13. 数据持久化方案解析(十三) —— 基于Unit Testing的Core Data测试(一)
14. 数据持久化方案解析(十四) —— 基于Unit Testing的Core Data测试(二)
15. 数据持久化方案解析(十五) —— 基于Realm和SwiftUI的数据持久化简单示例(一)
16. 数据持久化方案解析(十六) —— 基于Realm和SwiftUI的数据持久化简单示例(二)

开始

首先看下主要内容:

在本教程中,您将学习如何使用NSPersistentCloudKitContainer集成Core DataCloudKit。内容来自翻译。

首先看下写作环境:

Swift 5, iOS 14, Xcode 12

在将数据持久保存在应用程序中时,Core Data是一个不错的选择。自OS X TigeriOS 3以来,它一直是最古老,最成熟的Apple框架之一。

但是,跨设备同步Core Data的历史比较不稳定。 Apple在iOS 10中弃用了原始的iCloud sync API,直到iOS 13才引入NSPersistentCloudKitContainer,并没有替换它。 NSPersistentCloudKitContainer在您的Core Data存储和CloudKit(Apple的iCloud数据存储框架)之间架起了桥梁。

在本教程中,您将在名为PlaceTag的应用程序中集成CloudKitCore Data,该应用程序可为您访问的地方保存标题,描述和图像。

具体来说,您将学习如何:

  • iCloud中的旧Core Data切换到现代Core DataCloudKit
  • 在您的项目中设置CloudKit
  • 简化Core Data stack初始化。
  • CloudKit Dashboard仪表板中查询记录。

注意:本教程假定您对Core Data,对象图,数据持久性框架和iCloud有一定的了解。有关Core Data框架的更多信息,请参阅Getting Started with Core Data。

此外,iCloudCloudKit需要付费的Apple开发人员帐户才能运行。如果您未注册Apple Developer ProgramApple Developer Enterprise Program,则将无法跟进。

打开入门项目。 然后,在Xcode中打开starter项目。

选择一个开发团队,因为此步骤涉及根据developer ID设置数据,因此请选择您的(付费)团队以继续。 将bundle ID更新为组织内唯一的名称。 最后,选择一个iCloud容器名称。 此值应该是唯一的,并以iCloud开头。 Apple建议采用iCloud.{reverse DNS}.{app name or name for group}的形式。 例如:iCloud.com.yourcompany.yourapp

构建并运行。 您会看到一个空列表,准备好亲爱的日记!

点击Add Place按钮并创建一个条目。 如果需要,可以附加照片库中的图像。

完成后,点击Save Place,该位置将出现在列表中。

您可能已经注意到,Xcode在构建项目时会向您发出警告,指出NSPersistentStoreUbiquitousContentNameKeyiOS 10.0中已弃用。 这是因为该应用程序已经通过iCloud同步了Core Data,但是使用了不推荐使用的旧方法。 在更新到新的同步系统之前,您将接下来查看该代码。


Core Data Stack Initialization

在本教程中,您将把应用程序从旧的Core Data iCloud同步转换为新的CloudKit系统。 许多此类工作将在一个文件中进行。 打开CoreDataStack.swift并签出init()

private init() {
  //1
  guard let modelURL = Bundle.main
    .url(forResource: "PlaceTag", withExtension: "momd") else {
    fatalError("Error loading model from bundle")
  }

  //2
  guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
    fatalError("Error initializing mom from: \(modelURL)")
  }

  //3
  let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)

  //4
  context = NSManagedObjectContext(
    concurrencyType: .mainQueueConcurrencyType)

  //5
  context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

  //6
  context.persistentStoreCoordinator = psc

  do {
    //7
    try psc.addPersistentStore(
      ofType: NSSQLiteStoreType,
      configurationName: nil,
      at: CoreDataStack.storeURL,
      options: CoreDataStack.storeOptions)
  } catch {
    fatalError("Error adding store: \(error)")
  }
}

如果您使用的应用已经存在很长时间了,那么您可能会使用与此类似的代码来设置Core Data stack。这是此代码的逐步说明。

  • 1) 获取PlaceTag数据模型文件的编译版本的URL。该文件的未编译版本在项目中可作为PlaceTag.xcdatamodeld获得。
  • 2) 使用在步骤1中获得的URL初始化NSManagedObjectModel的实例。
  • 3) 从managed object model创建NSPersistentStoreCoordinator的实例。
  • 4) 使用NSManagedObjectContext初始化程序初始化CoreDataStack的上下文context,并将其并发类型作为mainQueueConcurrencyType传递。这会将managed object context绑定到主队列。
  • 5) 将上下文context的合并策略设置为NSMergeByPropertyObjectTrumpMergePolicyCore data会自动同步对象之间的合并冲突,并用新的内存中对象覆盖旧对象(Persistent Store版本的对象)。
  • 6) 将上下文context的持久性存储协调器Persistent Store Coordinator设置为您在步骤3中创建的上下文。
  • 7) 最后,将一个持久性存储persistent store添加到持久性存储协调器Persistent Store Coordinator中,从而完成Core Data stack的初始化。如果您没有遇到任何错误,现在就可以使用Core Data

storeOptions类属性包含前面提到的不赞成使用的NSPersistentStoreUbiquitousContentNameKey。这告诉系统将应用程序的iCloud文档目录的SQLite文件将保存在一个普遍存在的容器中。

那是很多步骤。您希望新系统更容易,对吗?在更新应用程序之前,是时候了解新方法与旧方法的区别了。

1. How Core Data in iCloud Used to Work

iCloud具有三种用于存储用户数据的系统:

  • 1) Key-value Storage:您可以保存离散值,例如简单的应用程序状态或首选项。
  • 2) Document Storage:使用此功能可保存基于用户可见文件的信息,例如图形和复杂的应用程序状态。
  • 3) CloudKit:用于管理结构化数据和在用户之间共享数据。

iOS上iCloudCore Data的第一次迭代是在文档存储之上进行的,并使用了相同的iCloud API

对于由SQLite支持的存储,Core Data提供了无处不在的持久存储。它使用了位于应用程序无处不在容器中的SQLite事务持久性机制。

在连接到iCloud帐户的每个设备上,您的应用程序的每个实例都维护自己的本地Core Data存储文件。换句话说,当数据在本地更改时,Core Data将更改日志文件写入到您应用的默认遍历容器中。

同样,当更改日志从连接到同一iCloud帐户的另一台设备到达时,Core Data会更新您应用的SQLite数据库本地副本。

看起来很有趣,但这种实现从未可靠地起作用。开发人员必须找到解决问题的方法,才能制定出可行的同步解决方案。这些问题使Apple意识到这是行不通的。

苹果公司推出iOS 10时,它已弃用iCloud中的Core DataNSPersistentStoreUbiquitousContentNameKey,并建议开发人员改用CloudKit。这使开发人员可以选择使用废弃的API或滚动自己的CloudKit支持的同步。

2. Core Data and CloudKit Today

iOS 13Xcode 11开始,Core Data项目的Xcode模板还可以选择集成CloudKit

Core DataCloudKit在定义中都具有三个主要元素: objects, models and stores。 简而言之,models描述objects,而stores则是objects持久化的地方。

查看下表:

您熟悉Core Data列中的项目。 在CloudKit命名法中,您可以将NSManagedObject的概念映射到CKRecord,将NSManagedObjectModel的概念映射到Schema,将NSPersistentStore的概念映射到CKRecordZoneCKDatabase

根据session in WWDC 2019的一次会议,苹果工程师编写了数千行代码来封装用于同步,调度和错误恢复的常用模式,以使Core DataCloudKit一起使用。 幸运的是,这次苹果表现出色。


Preparing PlaceTag for CloudKit

现在该转向新的Core Data CloudKit同步方法了。

1. Setting up the Project

在项目导航器中,单击PlaceTag项目。 选择PlaceTag target,然后单击Signing & Capabilities选项卡项,如下面的屏幕快照中的数字1到3所示。

取消选择iCloud Documents,然后选择CloudKit,如上面屏幕快照中的数字4所示。

许多应用程序和用户都可以访问iCloud。但是,称为容器的分区会隔离并封装数据以保持其私有性。

已经使用CloudKit的应用程序不能将Core DataCloudKit与它们现有的CloudKit容器一起使用。为了全面管理数据镜像的所有方面,Core Data拥有从Core Data模型创建的CloudKit schema。现有的CloudKit容器与此schema不兼容。

考虑到这一点,您需要创建一个新的容器。此值应该是唯一的,并以iCloud开头。如前所述,Apple建议采用iCloud.{reverse DNS}.{app name or name for group}的形式。例如:iCloud.com.yourcompany.yourapp

值得注意的是,公司的多个应用可以访问一个容器。

不幸的是,Apple不允许您以后删除容器,因此请明智地选择容器名称。

选择名称后,如果Xcode并未自动为您选择容器,请选择容器。

2. Adding Invisible Push Notification Support

每当容器中的记录发生更改时,CloudKit都会向已注册的设备发送静默推送通知。

要使用此功能,您需要向应用程序添加两项功能。如上图数字5所示,点击+按钮,然后添加Push Notification(如果Xcode并未自动为您添加)。

然后添加Background Modes

Background Modes部分中,选择Remote Notifications

Core DataCloudKit集成的优点在于,系统可以处理侦听和响应远程通知所需的所有工作。 如果您在没有Core Data的情况下使用CloudKit,则必须执行一些额外的步骤(超出本教程的范围)才能实现。

3. Migrating Existing Data

现在是时候将您的Core Data stack绑定到您刚创建的CloudKit容器了。 打开CoreDataStack.swift

您需要做的第一件事是更新和移动现有的数据库文件。 使用旧的同步系统,您的SQLite文件位于特殊位置,并且格式略有不同。 NSPersistentStoreCoordinator可以为您处理移动和更新它。 添加以下方法:

func migrateIfRequired(_ psc: NSPersistentStoreCoordinator) {
  //1
  if FileManager.default.fileExists(atPath: CoreDataStack.storeURL.path) {
    return
  }

  do {
    //2
    let store = try psc.addPersistentStore(
      ofType: NSSQLiteStoreType,
      configurationName: nil,
      at: CoreDataStack.storeURL,
      options: CoreDataStack.storeOptions)
    //3
    let newStore = try psc.migratePersistentStore(
      store,
      to: CoreDataStack.storeURL,
      options: [NSPersistentStoreRemoveUbiquitousMetadataOption: true],
      withType: NSSQLiteStoreType)
    //4
    try psc.remove(newStore)
  } catch {
    print("Error migrating store: \(error)")
  }
}

这是此代码的作用:

  • 1) 每次应用启动时,您都将调用此方法,但只需迁移一次。如果已经创建了store,那么您的工作就完成了。您可能想知道为什么使用与以前相同的store URL。使用您使用的iCloud同步时,数据库文件实际上并没有存储在您提供的URL中,而是存储在同一位置的一组复杂文件夹中。在新系统中,数据库文件将位于给定的URL中。
  • 2) 您可以像以前一样使用相同的选项创建persistent store。这次store是旧的iCloud同步存储。
  • 3) 您告诉persistent store coordinator将旧存储迁移到给定的URL,同时删除与iCloud相关的元数据。这是另一个不推荐使用的密钥,因此您将收到另一个警告,但是如果不使用不推荐使用的代码,则无法删除不推荐使用的功能!
  • 4) 最后,您从coordinator中删除刚创建的persistent store。这是因为迁移仅会发生一次,因此新的persistent store的实际设置将在此方法之外进行,并且您不应将两个persistent store链接到同一文件。

4. Modernizing the Core Data Stack

现在您已经准备好进行迁移,可以更新到更现代的Core Data stack。正如您所希望的那样,初始化过程已得到简化。您问如何精简?好吧,继续删除整个init()

添加类型为NSPersistentContainer的惰性属性:

private lazy var persistentContainer: NSPersistentContainer = {
  //1
  let container = NSPersistentContainer(name: "PlaceTag")
  //2
  migrateIfRequired(container.persistentStoreCoordinator)
  //3
  container.persistentStoreDescriptions.first?.url = CoreDataStack.storeURL
  //4
  container.loadPersistentStores { _, error in
    if let error = error as NSError? {
      fatalError("Unresolved error \(error), \(error.userInfo)")
    }
  }
  return container
}()

使用此代码的操作如下:

  • 1) NSPersistentContainer完成了init()之前所做的许多工作,包括拥有persistent store coordinator,加载模型和拥有managed object context。它会自动查找并加载您在初始化时使用的名称的数据模型。
  • 2) 调用新的迁移代码,并传递container拥有的persistent store coordinator
  • 3) 告诉容器为数据库文件使用特定的URL。此步骤是可选步骤,但由于已迁移了存储,因此在此需要此步骤,因此您希望控制文件位置。
  • 4) 调用loadPersistentStores(_ :)。此方法使用提供的模型加载或创建备份SQLite存储文件,并将其保存到磁盘上的适当位置。这是将persistent store链接到persistent store coordinator时。如果您之前没有删除已迁移的store,则此时会出现错误。

persistent container拥有用于UI的managed object context,因此,接下来,您需要从常量更改context的定义:

let context: NSManagedObjectContext

为一个计算属性:

var context: NSManagedObjectContext {
  persistentContainer.viewContext
}

您现在已经现代化了Core Data设置,并删除了旧的iCloud同步。

构建并运行。 您将看到之前添加的相同数据。

您仍然可以像以前一样添加新地点。 但是,您所有的数据现在都是本地的。

5. Moving to CloudKit

只要persistent storeNSSQLiteStoreType存储,并且使用的数据模型与CloudKit的限制兼容,使用Core Data的应用就可以移至CloudKit。 例如,CloudKit不支持唯一约束,未定义属性或所需关系(unique constraints, undefined attributes or required relationships)

NSPersistentContainer有一个子类,称为NSPersistentCloudKitContainer。 该persistent container能够管理由CloudKit支持的存储和非云存储。

通过将NSPersistentContainer替换为NSPersistentCloudKitContainerPlaceTagCore Data stack将启用CloudKit。 过渡就像云本身一样柔和而流畅! 接下来就是您要做的。

更改persistentContainer属性以反映这些更改。 将所有NSPersistentContainer引用替换为NSPersistentCloudKitContainer

private lazy var persistentContainer: NSPersistentCloudKitContainer = {

let container = NSPersistentCloudKitContainer(name: "PlaceTag")

还要在返回容器之前添加以下行:

container.viewContext.automaticallyMergesChangesFromParent = true

您无需在此进行太多更改,只需更改类和automaticallyMergesChangesFromParent即可。 通过设置此标志,UI将反映自动同步,该同步几乎是通过无声推送通知发生的。

构建并运行。 一切似乎都没有改变。 但是,这一次,如果您在要测试的设备上登录到iCloud,则iCloud会同步添加到PlaceTag的位置。 您可以通过在两台设备上运行该应用程序来验证这一点。 万岁!

注意:确保在真实设备上测试同步。 当涉及静默推送通知时,模拟器并不可靠。


Viewing the Project in CloudKit Dashboard

该应用程序现在应该将您的位置同步到CloudKit,但是下一步是验证它确实是。

打开CloudKit Dashboard,并使用您在测试设备上使用的相同Apple ID登录。 CloudKit仪表板向您致意。

您会在左侧看到为应用程序创建的容器,在右侧看到每个容器的可用选项。

选择PlaceTag的容器,然后单击Data。 将显示Records页面。

在此页面上,选择com.apple.coredata.cloudkit.zone作为zone。 现在,您可以使用过滤器查询可用数据。

选择查询Query按钮,然后选择CD_Place作为Type

CD_是什么? CD代表Core Data。 苹果正在繁重地将Core Data转换为CloudKit对象。 此前缀是他们防止命名空间冲突的工作的一部分。

Filter下,选择Custom,并查询具有CD_Title of Iceland的记录。 如果您创建了一个带有其他标题的地方,请在此处键入。

单击Save,然后单击Query Records

您在应用中创建的对象将显示在此处。 您可以编辑或删除对象,更改将通过您设置的远程推送通知反映在应用程序中。

恭喜你!您已成功使用CloudKit转换PlaceTag,因此其数据可在多个设备上持久保存。

您已成功将CloudKit支持添加到您的Core Data应用程序中。尽管CloudKit是一个复杂的框架,但Apple仍使它易于使用-至少与Core Data一起使用。

如前所述,Core Data是一个非常古老的Apple框架。它具有许多功能,超出了本教程的范围。 CloudKit尽管相对较新,但也具有许多有趣的功能。

使用PlaceTag,您几乎不会看到Core DataCloudKit的表面。模型中的实体与其他实体没有关系。您没有涉及自定义通知处理和许多其他功能。

有关Core DataCloudKit的大量资源,您可以用来更深入地探讨此主题,包括:

  • raywenderlich.com的教程团队编写的关于Core Data by Tutorials 的书。
  • CloudKit Tutorial: Getting Started,深入了解CloudKit的教程。
  • Beginning CloudKit:一个视频教程系列,可帮助您深入学习该主题。
  • raywenderlich.com Forums:向我们很棒的社区寻求帮助。

后记

本篇主要讲述了基于NSPersistentCloudKitContainerCore DataCloudKit的集成示例,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(数据持久化方案解析(十七) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(一))