版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.11.11 星期三 |
前言
开始
首先看下主要内容:
首先看下写作环境:
在将数据持久保存在应用程序中时,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
仪表板中查询记录。
打开入门项目。 然后,在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:向我们很棒的社区寻求帮助。