版本记录
版本号 | 时间 |
---|---|
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
的集成示例,感兴趣的给个赞或者关注~~~