数据持久化方案解析(十七) —— 基于NSPersistentC
版本记录
| 版本号 | 时间 |
|---|---|
| V1.0 | 2020.11.11 星期三 |
前言
数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说
plist文件(属性列表)、preference(偏好设置)、NSKeyedArchiver(归档)、SQLite 3、CoreData,这里基本上我们都用过。这几种方案各有优缺点,其中,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 Data和CloudKit。内容来自翻译。
首先看下写作环境:
Swift 5, iOS 14, Xcode 12
在将数据持久保存在应用程序中时,Core Data是一个不错的选择。自OS X Tiger和iOS 3以来,它一直是最古老,最成熟的Apple框架之一。
但是,跨设备同步Core Data的历史比较不稳定。 Apple在iOS 10中弃用了原始的iCloud sync API,直到iOS 13才引入NSPersistentCloudKitContainer,并没有替换它。 NSPersistentCloudKitContainer在您的Core Data存储和CloudKit(Apple的iCloud数据存储框架)之间架起了桥梁。
在本教程中,您将在名为PlaceTag的应用程序中集成CloudKit和Core Data,该应用程序可为您访问的地方保存标题,描述和图像。
具体来说,您将学习如何:
- 从
iCloud中的旧Core Data切换到现代Core Data和CloudKit。 - 在您的项目中设置
CloudKit。 - 简化
Core Data stack初始化。 - 在
CloudKit Dashboard仪表板中查询记录。
注意:本教程假定您对
Core Data,对象图,数据持久性框架和iCloud有一定的了解。有关Core Data框架的更多信息,请参阅Getting Started with Core Data。此外,
iCloud和CloudKit需要付费的Apple开发人员帐户才能运行。如果您未注册Apple Developer Program或Apple 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在构建项目时会向您发出警告,指出NSPersistentStoreUbiquitousContentNameKey在iOS 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的合并策略设置为NSMergeByPropertyObjectTrumpMergePolicy。Core 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上iCloud中Core Data的第一次迭代是在文档存储之上进行的,并使用了相同的iCloud API。
对于由SQLite支持的存储,Core Data提供了无处不在的持久存储。它使用了位于应用程序无处不在容器中的SQLite事务持久性机制。
在连接到iCloud帐户的每个设备上,您的应用程序的每个实例都维护自己的本地Core Data存储文件。换句话说,当数据在本地更改时,Core Data将更改日志文件写入到您应用的默认遍历容器中。
同样,当更改日志从连接到同一iCloud帐户的另一台设备到达时,Core Data会更新您应用的SQLite数据库本地副本。
看起来很有趣,但这种实现从未可靠地起作用。开发人员必须找到解决问题的方法,才能制定出可行的同步解决方案。这些问题使Apple意识到这是行不通的。
苹果公司推出iOS 10时,它已弃用iCloud中的Core Data和NSPersistentStoreUbiquitousContentNameKey,并建议开发人员改用CloudKit。这使开发人员可以选择使用废弃的API或滚动自己的CloudKit支持的同步。
2. Core Data and CloudKit Today
从iOS 13和Xcode 11开始,Core Data项目的Xcode模板还可以选择集成CloudKit。
Core Data和CloudKit在定义中都具有三个主要元素: objects, models and stores。 简而言之,models描述objects,而stores则是objects持久化的地方。
查看下表:
您熟悉Core Data列中的项目。 在CloudKit命名法中,您可以将NSManagedObject的概念映射到CKRecord,将NSManagedObjectModel的概念映射到Schema,将NSPersistentStore的概念映射到CKRecordZone或CKDatabase。
根据session in WWDC 2019的一次会议,苹果工程师编写了数千行代码来封装用于同步,调度和错误恢复的常用模式,以使Core Data与CloudKit一起使用。 幸运的是,这次苹果表现出色。
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 Data和CloudKit与它们现有的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 Data和CloudKit集成的优点在于,系统可以处理侦听和响应远程通知所需的所有工作。 如果您在没有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 store是NSSQLiteStoreType存储,并且使用的数据模型与CloudKit的限制兼容,使用Core Data的应用就可以移至CloudKit。 例如,CloudKit不支持唯一约束,未定义属性或所需关系(unique constraints, undefined attributes or required relationships)。
NSPersistentContainer有一个子类,称为NSPersistentCloudKitContainer。 该persistent container能够管理由CloudKit支持的存储和非云存储。
通过将NSPersistentContainer替换为NSPersistentCloudKitContainer,PlaceTag的Core 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 Data和CloudKit的表面。模型中的实体与其他实体没有关系。您没有涉及自定义通知处理和许多其他功能。
有关Core Data和CloudKit的大量资源,您可以用来更深入地探讨此主题,包括:
-
raywenderlich.com的教程团队编写的关于Core Data by Tutorials 的书。 -
CloudKit Tutorial: Getting Started,深入了解
CloudKit的教程。 - Beginning CloudKit:一个视频教程系列,可帮助您深入学习该主题。
- raywenderlich.com Forums:向我们很棒的社区寻求帮助。
后记
本篇主要讲述了基于
NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例,感兴趣的给个赞或者关注~~~