Highlight
Core Data can infer many schema migrations from source and destination models automatically, while CloudKit-backed stores still require additive schema design, Development initialization, and Production promotion.
核心内容
Core Data 的数据模型一改,磁盘上的存储结构也要跟着改。
(01:00)Session 用一个 Aircraft 实体说明问题:模型里有 type 和 numberOfEngines,SQLite 存储里也有对应列;当你再加 numberOfPassengers,底层存储也必须增加对应结构。
如果不迁移,Core Data 会拒绝打开旧 store。错误码是 NSPersistentStoreIncompatibleVersionHashError(01:36)。这个错误表示当前模型和 store 创建时的模型不匹配。
Apple 推荐优先使用轻量迁移(lightweight migration)。它会比较 source managed object model 和 destination managed object model,自动生成 mapping model,把模型变化落到数据库 schema 里(01:59)。
这场演讲的重点有三件事:
- 哪些 Core Data 模型变化可以交给轻量迁移。
- 复杂变化如何拆成多次轻量迁移。
- Core Data 与 CloudKit 同步时,schema 迁移受到哪些限制。
详细内容
1. 轻量迁移能处理哪些变化
(02:54)轻量迁移覆盖的属性变化包括:新增属性、删除属性、把非可选属性改成可选、把可选属性改成非可选并提供默认值、重命名属性。
重命名要设置 renaming identifier。比如把 Aircraft.color 改成 Aircraft.paintColor,destination model 里的 paintColor 需要把 renaming identifier 设置为 source model 里的 color(03:11)。Core Data 用这个标识建立稳定的规范名称,支持从 version 1 到 version 3 这种跨版本路径。
关系和实体也可以迁移。关系可以新增、删除、重命名,也可以在 to-one / to-many、ordered / unordered 之间改变 cardinality(04:00)。实体可以新增、删除、重命名,也可以在继承层级里上下移动属性,或把实体移入、移出层级(04:29)。
一个限制要记住:两个 source model 中没有共同父实体的既有实体,不能在 destination model 中合并到同一个父实体下面(04:53)。
轻量迁移的两个关键选项是:
NSMigratePersistentStoresAutomaticallyOption
NSInferMappingModelAutomaticallyOption
关键点:
NSMigratePersistentStoresAutomaticallyOption:允许 Core Data 在发现 store 和当前模型不匹配时执行迁移。NSInferMappingModelAutomaticallyOption:允许 Core Data 根据模型差异推断 mapping model。- 如果使用
NSPersistentContainer或NSPersistentStoreDescription,这两个选项会自动设置(05:31)。 - 如果直接使用
NSPersistentStoreCoordinator.addPersistentStore(...),需要手动传入 options dictionary。
2. 手动打开 store 时如何启用轻量迁移
(06:16)Session 给出的代码展示了第三种做法:继续使用 NSPersistentStoreCoordinator,但在添加 persistent store 时传入迁移选项。
import CoreData
let storeURL = NSURL.fileURL(withPath: "/path/to/store")
let momURL = NSURL.fileURL(withPath: "/path/to/model")
guard let mom = NSManagedObjectModel(contentsOf: momURL) else {
fatalError("Error initializing managed object model for URL: \(momURL)")
}
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: mom)
do {
let opts = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: Optional<String>.none,
at: storeURL,
options: opts)
} catch {
fatalError("Error configuring persistent store: \(error)")
}
关键点:
import CoreData:使用 Core Data 的 managed object model、coordinator 和 store option。storeURL:指向已有 persistent store 的路径。momURL:指向当前应用要使用的 managed object model。NSManagedObjectModel(contentsOf:):从模型文件加载 destination model;加载失败时直接停止,因为后续迁移没有模型依据。NSPersistentStoreCoordinator(managedObjectModel:):用当前模型创建 coordinator。NSMigratePersistentStoresAutomaticallyOption: true:打开自动迁移。NSInferMappingModelAutomaticallyOption: true:允许 Core Data 推断 mapping model。addPersistentStore(...):真正打开 store;如果 store 的模型版本不匹配,迁移会在这里发生。catch:捕获打开 store 或迁移失败的错误。
如果想提前检查 Core Data 能不能推断 mapping model,可以调用 NSMappingModel.inferredMappingModel(06:56)。它能推断就返回 mapping model,不能推断就返回 nil。
3. 复杂变化拆成多次轻量迁移
(07:19)有些组合变化超出轻量迁移能力。演讲用 flightData 举例:原来二进制数据使用 external storage,后来想改成内部存储并删除外部文件路径。直接从模型 A 迁到模型 B,不符合轻量迁移规则。
做法是引入桥接模型,把一个复杂迁移拆成多个 Core Data 能理解的步骤(08:16)。
Session 的拆法是:
- 从模型 A 创建 A’,新增临时属性
tmpStorage。 - 在两次迁移之间运行应用自己的导入逻辑,把外部文件数据写入
tmpStorage。 - 从 A’ 创建 A”,删除旧的 external storage 属性,同时把新属性重命名为最终名称。
每一步都落在轻量迁移能力范围内。Core Data 负责 schema 迁移,外部文件导入这种业务逻辑由应用自己执行(09:26)。
关键点:
- 桥接模型的目标是把不可迁移变化拆小,而不是保留给用户长期使用。
- 应用特定的导入逻辑要放在两次轻量迁移之间。
- 如果迁移过程可能被系统终止,导入逻辑必须可重启(restartable)(10:19)。
- 可以构建一个循环,按串行顺序打开未处理模型,让 Core Data 逐步迁移 store(10:04)。
4. CloudKit 同步时的迁移限制
(10:34)Core Data 与 CloudKit 同步时,双方需要共享对数据模型的理解。Core Data model editor 中定义的模型会用于生成 CloudKit schema。schema 先生成到 Development 环境,再提升到 Production。
CloudKit 不支持 Core Data 模型里的所有能力(11:07)。Session 明确列出几类限制:
- entity 的 unique constraints 不支持。
Undefined和objectIDattribute type 不支持。- relationship 必须是 optional,并且必须有 inverse relationship。
- deny deletion rule 不支持。
Production schema 还有更硬的约束。CloudKit schema 提升到 Production 后,record types 和 fields 是不可变的(11:43)。CloudKit 支持的是新增字段和新增 record type;不能修改或删除既有 record type 或 field(12:07)。
这影响 Core Data 迁移的边界。轻量迁移只会改变本地 store 文件,不会自动修改 CloudKit schema(12:26)。如果模型用于 CloudKit,还需要运行 schema initializer,把变化写入 Development database,再用 CloudKit Console 提升到 Production。
Session 给了三种 CloudKit schema 演进策略(13:33):
- 增量地给既有 record type 添加新 field。
- 在实体中加入 version attribute,用 fetch request 只取当前 app 版本兼容的记录。
- 使用
NSPersistentCloudKitContainerOptions把新 store 关联到新的 container。
关键点:
- 第一种策略让旧版本 app 仍能访问用户创建的记录,但看不到所有字段。
- 第二种策略会让旧版本 app 隐藏较新版本创建的不兼容记录。
- 第三种策略会重新上传数据集;如果用户数据量大,上传可能持续较长时间。
- 任何策略都要测试不同 app 版本与不同数据模型版本共存的情况(14:33)。
5. 调试迁移过程
(15:21)演示部分打开了两个环境变量:com.apple.CoreData.SQLDebug 和 com.apple.CoreData.MigrationDebug。Core Data 会输出创建文件、写入 store metadata、materialize schema、执行迁移等步骤。
还可以用 sqlite3 命令行工具检查 SQLite store。演示里先确认 ZLOGENTRY 表包含原始模型对应的列,再修改模型、启用迁移、重新运行应用,最后查看新 schema 已经由 Core Data 自动落盘(15:52)。
关键点:
NSPersistentStoreIncompatibleVersionHashError是迁移入口信号。SQLDebug帮你确认底层 schema 是否按预期创建或更新。MigrationDebug帮你观察 Core Data 是否真正执行迁移。sqlite3可以独立验证 store 文件中的表和列。
核心启发
1. 做一个模型迁移预检工具
- 做什么:在开发构建里,对当前模型和历史模型运行迁移能力检查。
- 为什么值得做:
NSMappingModel.inferredMappingModel可以在真正迁移前判断 Core Data 是否能推断 mapping model。 - 怎么开始:收集 app bundle 中的历史 managed object model,逐对调用
NSMappingModel.inferredMappingModel,失败时在 CI 或本地调试面板中标出具体版本。
2. 给 CloudKit-backed 模型加兼容性检查
- 做什么:在模型审查脚本里检查 CloudKit 不支持的 Core Data 设计。
- 为什么值得做:CloudKit 不支持 unique constraints、
Undefined、objectID、缺少 inverse 的 relationship、deny deletion rule。 - 怎么开始:遍历
NSManagedObjectModel的 entities、attributes、relationships 和 deletion rules,把不兼容项输出为构建警告。
3. 设计可重启的数据导入迁移
- 做什么:把外部文件导入、字段补写等迁移步骤做成可重复执行的任务。
- 为什么值得做:Session 明确要求应用特定迁移逻辑在进程终止后可重启。
- 怎么开始:给每条待处理对象记录迁移状态,导入成功后再标记完成;下次启动时只处理未完成对象。
4. 为多版本 app 建立 CloudKit 回归数据集
- 做什么:用旧版本和新版本 app 共同访问同一 CloudKit Development container,验证 record type、field 和 version attribute 策略。
- 为什么值得做:用户设备会同时运行旧版本和新版本 app,Production schema 又是 additive 的。
- 怎么开始:先在 Development 环境新增 field 或 record type,运行旧版本 app 创建和读取记录,再运行新版本 app 写入新字段,检查旧版本的显示和 fetch 结果。
关联 Session
- Optimize your use of Core Data and CloudKit — 继续讲 Core Data 与 CloudKit 同步的测试、数据集生成和工作流优化。
- What’s new in CloudKit Console — 介绍 CloudKit Console 中用于查看、调试和管理 container 的能力。
- Meet CKTool JS — 展示如何用 CKTool JS 自动化 CloudKit schema 和 record 管理流程。
评论
GitHub Issues · utterances