大多数浏览器和
Developer App 均支持流媒体播放。
-
Core Data 新功能
通过 Core Data 的改进提高 App 数据的持久性。了解如何使用复合属性来创建更直观的数据模型。我们还将向你展示如何通过破坏性变化来迁移模式、何时推迟密集迁移以及如何避免个人设备上的开销。为了充分理解本期视频,你要熟悉如何处理 Core Data 中的不同数据类型以及轻量级迁移的基础知识。
章节
- 0:00 - Intro
- 0:56 - Composite attributes
- 6:31 - Stage your migrations
- 18:23 - Defer your migrations
- 22:33 - Wrap-up
资源
相关视频
WWDC22
-
下载
♪ ♪
David:大家好 欢迎收看 “Core Data 新功能” 我是 Core Data 团队的工程师 David Stites 在本次讲座中 你将了解到 Core Data 中的新技术 这些技术将帮助你 更快更容易地设计 查询 更新和迁移 App 中的 Core Data 数据模型
我将首先介绍复合属性 一种在你的 App 模型中 整理结构化数据不错的新方法 然后再谈谈如何 “分段式”的复杂模型迁移 以便你可以使用轻量级迁移 最后我将介绍 如何延迟你的模型迁移 以保持 App 的响应速度 复合属性是一种新的属性类型
复合属性允许在单一属性中 封装复杂和自定义的数据类型
每个复合属性都是由你已经熟悉的 内置 Core Data 类型的属性组成的 如 String、Float、Int 和 Data
复合属性可以相互嵌套 因此顶级的复合属性 可以包含额外的复合属性
Xcode Core Data 模型编辑器已经更新 以便于定义和管理 你的模型的复合属性
复合属性是 使用 transformable 类型属性 是创建持久的 自定义数据类型的出色的替代方案 不需要编写转换属性值的代码 与 transformable 属性不同 复合属性允许带有 NSPredicate 的 NSFetchRequest 它是用复合属性的 命名空间的键路径配置的
复合属性可以用来封装 大量的扁平化属性 使代码更容易阅读、更易维护
复合属性可以用来提高 App 的性能 如果你的数据模型的 组成方式使获取一个实体 总会导致访问与另一个实体的关系 你可以使用复合属性 来重构这种关系 在第一个实体中 嵌入复合属性的效果是 它可以防止跨关系对象中出现失效
复合属性类是 NSCompositeAttributeDescription NSCompositeAttributeDescription 的 属性类型是 NSCompositeAttributeType
NSCompositeAttributeDescription 类 包含一个 elements 数组 该数组由 NSAttributeDescription 类属性 或其他嵌套的 NSCompositeAttributeDescription 类属性组成
elements 数组不能包含 其他类型的属性描述 如 NSRelationshipDescription 试图设置无效的 elements 会导致 NSInvalidArgumentException 异常
我将用一个演示 向你描述如何采用复合属性
想象一个带有 Aircraft 实体的 基本数据模型 它有许多属性 包括一个 transformable 类型的颜色属性 该类型的转换器 会存储并解析一个格式化的字符串 描述 aircraft 的 原色、次色和第三色
我将改进这个实体 将颜色属性 替换为一个复合属性 colorScheme 来存储 aircraft 的颜色
colorScheme 是包含 elements 的复合属性 primary、secondary 和 tertiary 这些都是 String 属性
在 Xcode 中 我会打开一个项目 一个用来跟踪飞行时间的 App
该 App 的数据模型 配置了我刚才谈到的 Aircraft 实体 及其他几个实体
为了开始转换 在 Core Data 模型编辑器中 我会添加一个名为 colorScheme 的新复合属性
在这个复合属性中 我将添加三个 string 属性 primary、secondary 和 tertiary
在 Aircraft 实体中 我会添加复合属性 并将该属性的类型 设置为 colorScheme
模型中的工作已经完成 是时候更新代码了
在我的 Aircraft 实例中 我会添加一个新的属性 @NSManaged var colorScheme 其类型是一个带有 String 键值和 Any 对象的字典 因为我在代码中 使用了这个复合属性 所以我会使用字典表示法来访问值 使用属性名作为键值 在这里 我通过使用 String 键值 primary、secondary 和 tertiary 来设置 aircraft 的 colorScheme 属性
同样 当我用 NSPredicate 配置 NSFetchRequest 时 复合属性的元素 是通过命名空间的键路径访问的
这里 colorScheme.primary 被用来筛选该属性
随着 App 的发展 可能需要改变数据模型
更新数据模型 需要在底层存储模式中 实现这些变化
如果将 numPassengers 属性 新增至模型中 则必须更新相应的存储
执行表结构变化的过程被称为迁移
迁移之后 这些变化 会全部反映在底层存储中
Core Data 有内置的迁移工具集 帮助 App 的数据存储 与当前的数据模型保持同步 这些工具被统称为“轻量级迁移”
要了解更多关于轻量级迁移的信息 请观看 WWDC 2022 中的 “Core Data 模式进化”
有时 数据模型的综合变化 超过了轻量级迁移的能力 解决这个问题的方法是分段迁移
分段迁移 API 的设计有几个目标: 帮助你迁移那些有无法适用 轻量级模式变化的复杂数据模型 有可能通过移除与迁移和迁移基础架构 相关的大量代码来简化你的 App 以及为你的 App 提供在迁移过程中 通过执行各种任务 以获得执行控制的机会
要使用这个 API 你需要采取以下几个步骤: 确定你的模型变化何时 不符合轻量级迁移所支持的操作 将不符合要求的模型变化分解为 轻量级迁移所支持的 一系列符合要求的模型变化 使用新的分阶段迁移 API 向 Core Data 描述 NSManagedObjectModel 的全序关系 让 Core Data 执行事件循环 以串行顺序迭代 每个未处理的模型完成存储迁移 在迁移过程中的某些时刻 执行控制 让你的 App 可以执行 任何与该迁移相关的必要任务
要确定你的模型何时 有不符合要求的轻量级变化 你有几个选择 第一个选择是手动审查模式变化 确保每个变化 都符合轻量级迁移的条件
第二个选择是尝试设置新的模型 和轻量级迁移选项 NSMigratePersistentStores AutomaticallyOption 和 NSInferMappingModelAutomaticallyOption 为 true 打开持久性存储 如果这些变化不符合轻量级要求 你会收到 NSPersistentStore IncompatibleVersionHashError
最后一个选择是使用 NSMappingModel.inferredMappingModel (forSourceModel:destinationModel:) 如果 Core Data 能够创建推断模型 这个方法就会返回该模型 否则 它将返回 nil
再思考一下 Aircraft 模型 它有一个新的属性 flightData 它以二进制格式存储数据
假设需要让这个模型非规范化 并将所有的飞行数据 分离到它自己的实体类型中 同时还要保留所有的现有数据 以及生成数据的 aircraft 的关系 这是一个非常复杂的模型变化 而且它本身不符合轻量级迁移的要求 这些变化需要被分解 以使用分阶段迁移 在分解非轻量级变化时 目标是将 不符合轻量级迁移的迁移任务 转化为符合轻量级迁移的 最小系列的迁移
引入的每个模型 都会有一个或多个操作 这些操作都在 轻量级迁移的能力范围内 组成了不符合要求的变化 结果是一系列的迁移 其中每个模型都可进行轻量级迁移 总体结果等同于不符合要求的迁移
回到这个例子 我把原始模型标注为 ModelV1
这个模型迁移 将通过引入两个新的模型版本 ModelV2 和 ModelV3 进行分解
在 ModelV2 中 Aircraft 实体 获得了一个 flightParameters 关系 它是一个新创建的 FlightData 实体的集合 FlightData 实体 有二进制类型的属性数据 和与 Aircraft 的关系 为了保留现有的数据 迁移阶段将把 Aircraft 实体的数据 复制到新的 FlightData 实体中 并把它们与 Aircraft 联系起来
我们的最终模型是 由 ModelV2 创建的 ModelV3 在 ModelV3 中 旧的 flightData 属性 将被从 Aircraft 实体中删除 该模型被成功地去规范化 所有的现有数据被保留下来 所描述的每个步骤 都在轻量级迁移的能力范围内
为了描述模型的 total ordering Core Data 框架级别的支持 应由以下类组成: NSStagedMigrationManager、 NSCustomMigrationStage、 NSLightweightMigrationStage 和 NSManagedObjectModelReference
NSStagedMigrationManager 类 封装了由你描述的 NSCustomMigrationStage 和附属 NSLightweightMigrationStage 的 全序关系 分阶段迁移管理器 还管理迁移事件循环 并通过 NSPersistentContainer 提供对迁移存储空间的访问 使用 NSPersistentStoreStagedMigrationManagerOptionKey 密钥 该管理器被添加到存储选项中
迁移阶段构成了 模型在不同版本之间迁移的基础
当你采用分阶段迁移时 你将使用 NSCustomMigrationStage 或 NSLightweightMigrationStage 向 Core Data 描述每个模型版本 NSLightweightMigrationStage 类 描述了一系列 不需要分解的、 符合轻量级迁移条件的模型 你的大多数模型可能都属于此类 这些轻量级迁移阶段被用于补充 向 Core Data 描述的模型的全序关系 所有的轻量级模型版本必须 在一个或多个 NSLightweightMigrationStage 中表示
你创建的模型的各个分解版本 将用一个 NSCustomMigrationStage 表示 并包含源模型引用和目标模型引用
NSCustomMigrationStage 提供了可选的处理程序 这些处理程序会在 迁移阶段之前和之后立即运行 这些处理程序让你可以 在迁移过程中运行自定义代码
分阶段迁移使用了 NSManagedObjectModelReference 类 这个类代表了 NSManagedObjectModel 的承诺 在迁移过程中 Core Data 将履行这个承诺 NSManagedObjectModelReference 是灵活的 可以以多种不同的方式创建
每个 NSManagedObjectModelReference 都需要 用一个版本校验和来初始化 这是为了验证模型 没有在无意中更改 该校验和可以通过 NSManagedObjectModel.versionChecksum 方法获得
另外 你也可以 从 Xcode 构建日志中的 “Compile data model” 中 检索到版本校验 搜索字符串“version checksum”
对于不同版本的模型 校验和也可以 在 NSManagedObjectModel 包中的 VersionInfo.plist 找到
回到例子中 为了开始使用分阶段迁移 首先 我会为 全部三个模型创建模型引用 我使用的是接受模型名称 和捆绑引用的初始化器 不过也有其他选择
下一步是描述所需的迁移阶段 由于第一阶段 只添加了 flightData 属性 它可以用一个轻量级阶段来表示 因为添加属性是一个轻量级变化
下一个阶段是一个自定义阶段 因为模型变化 会被分解为两个模型版本 我们需要运行 自定义代码来保留现有数据 自定义迁移阶段是用 ModelV2 和 ModelV3 初始化的
在 willMigrateHandler 中 代码获取了 flightData 不为 nil 的实体行 通用的 NSManagedObject 和 NSFetchRequestResult 类型 取代了 Aircraft 托管对象子类 因为在迁移过程中 Aircraft 类 有可能不会像预期一样存在
对于每个被提取的 Aircraft 实体 数据会被复制到 一个新的 FlightData 实例中 并且这两个实体会被关联并持久化 在迁移阶段的执行结束后 存储模式会被更新为最新的模型 并且现有的数据会被保留下来
为了完成分阶段迁移 我创建了一个带有轻量级迁移阶段 和自定义迁移阶段的 NSStagedMigrationManager
NSStagedMigrationManager 被添加到 NSPersistentStoreDescription 选项中 密钥为 NSPersistentStoreStagedMigrationManagerOptionKey
然后加载持久性存储以开始迁移过程 并影响存储模式 这样就好了 Core Data 会自动应用所需的阶段 并迁移存储模式
一些轻量级迁移 需要额外的运行时间 你的 App 可能无法在前台提供
在轻量级迁移过程中 转换用户数据的过程 不是瞬间完成的 例如 如果迁移涉及 将数据从一列复制到另一列 或从一个表复制到另一个表 这可能需要一些时间
这会导致令人沮丧的用户体验 特别是如果迁移在运行时进行
延迟迁移可以帮助你解决这个问题 这个 API 让你可以推迟 轻量级迁移过程中的 一些工作 并能够在之后完成推迟的工作 在轻量级迁移过程中 如果一个实体有需要清理的 迁移转换 比如更新索引 或在执行表复制后删除一个列 这个表的转换可以被推迟 直到你认为资源可用于执行表的转换 轻量级迁移仍然同步 并且正常进行 只有模式的清理被推迟了 你的 App 将正常使用最新的模式 要选择延迟迁移 请将存储选项中的 NSPersistentStoreDeferredLightweightMigrationOptionKey 设置为 true
延迟迁移 API 的运行时兼容性 可以一直追溯到 macOS Big Sur 和 iOS 14
延迟迁移仅适用于 SQLite 存储类型
延迟迁移可能有用的一些例子包括: 从实体中删除属性或关系 改变实体层次结构不再存在的关系 以及将关系从有序变为无序
为了完成延迟迁移任务 请检查持久性存储元数据 如果其包含密钥 NSPersistentStoreDeferredLightweightMigrationOptionKey 则代表有延迟迁移工作 需要完成 延迟迁移可以通过调用 NSPersistentStoreCoordinator.finishDeferredLightweightMigration 来处理
要推迟你的 App 中的任何轻量级迁移 在向协调器添加持久性存储时 在你的存储选项中设置 NSPersistentStoreDeferredLightweightMigrationOptionKey 为 true
到了完成延迟迁移的好时机 你可以通过检查存储的元数据来查看 是否有挂起的延迟工作 如果 NSPersistentStoreDeferredLightweightMigrationOptionKey 被设置为 true 则调用 finishDeferredLightweightMigration()
为了安排你的延迟迁移任务 考虑使用 Background Tasks API BGProcessingTask 是为了耗时的操作 如长时间的数据更新和 App 维护 系统会决定 运行你的任务的最佳时间 一般来说 处理任务只在设备空闲时运行 当用户开始使用设备时 任何后台处理任务都会终止
延迟迁移和分阶段迁移 可以结合使用 如果你有一组既复杂又耗时的迁移 可以考虑设计一些阶段 来利用这两种 API 的能力 回到示例模型 在 ModelV3 中 我们删除了 flightData 属性 它就很适合延迟迁移
Core Data 中有三个出色的新技术 使用复合属性以一种 可嵌套的、结构化的方式 封装你的自定义数据类型; 通过分解你的模型变化 使用分阶段迁移 来执行复杂的模型迁移; 以及通过使用延迟迁移 推迟一些迁移工作 来提高你的 App 性能 这三种技术可以协同工作 以改进你的 App
我们团队非常高兴 听到你如何利用这些新技术 感谢你的观看 愿你享受 WWDC ♪ ♪
-
-
5:39 - Adding a composite attribute
enum PaintColor: String, CaseIterable, Identifiable { case none, white, blue, orange, red, gray, green, gold, yellow, black var id: Self { self } } extension Aircraft { @nonobjc public class func fetchRequest() -> NSFetchRequest<Aircraft> { return NSFetchRequest<Aircraft>(entityName: "Aircraft") } @NSManaged public var aircraftCategory: String? @NSManaged public var aircraftClass: String? @NSManaged public var aircraftType: String? @NSManaged public var colorScheme: [String: Any]? @NSManaged public var photo: Data? @NSManaged public var tailNumber: String? @NSManaged public var logEntries: NSSet? }
-
5:53 - Setting a composite attribute
private func addAircraft() { viewContext.performAndWait { let newAircraft = Aircraft(context: viewContext) newAircraft.tailNumber = tailNumber newAircraft.aircraftType = aircraftType newAircraft.aircraftClass = aircraftClass newAircraft.aircraftCategory = aircraftCategory newAircraft.colorScheme = [ "primary": primaryColor.rawValue, "secondary": secondaryColor.rawValue, "tertiary": tertiaryColor.rawValue ] do { try viewContext.save() } catch { // ... } } }
-
6:11 - Fetching a composite attribute
private func findAircraft(with color: String) { viewContext.performAndWait { let fetchRequest = Aircraft.fetchRequest() fetchRequest.predicate = NSPredicate(format: "colorScheme.primary == %@", color) do { var fetchedResults: [Aircraft] fetchedResults = try viewContext.fetch(fetchRequest) // ... } catch { // Handle any errors that may occur } } }
-
16:00 - Creating managed object model references for staged migration
let v1ModelChecksum = "kk8XL4OkE7gYLFHTrH6W+EhTw8w14uq1klkVRPiuiAk=" let v1ModelReference = NSManagedObjectModelReference( modelName: "modelV1" in: NSBundle.mainBundle versionChecksum: v1ModelChecksum ) let v2ModelChecksum = "PA0Gbxs46liWKg7/aZMCBtu9vVIF6MlskbhhjrCd7ms=" let v2ModelReference = NSManagedObjectModelReference( modelName: "modelV2" in: NSBundle.mainBundle versionChecksum: v2ModelChecksum ) let v3ModelChecksum = "iWKg7bxs46g7liWkk8XL4OkE7gYL/FHTrH6WF23Jhhs=" let v3ModelReference = NSManagedObjectModelReference( modelName: "modelV3" in: NSBundle.mainBundle versionChecksum: v3ModelChecksum )
-
16:19 - Creating migration stages for staged migration
let lightweightStage = NSLightweightMigrationStage([v1ModelChecksum]) lightweightStage.label = "V1 to V2: Add flightData attribute" let customStage = NSCustomMigrationStage( migratingFrom: v2ModelReference, to: v3ModelReference ) customStage.label = "V2 to V3: Denormalize model with FlightData entity"
-
16:54 - willMigrationHandler and didMigrationHandler of NSCustomMigrationStage
customStage.willMigrateHandler = { migrationManager, currentStage in guard let container = migrationManager.container else { return } let context = container.newBackgroundContext() try context.performAndWait { let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Aircraft") fetchRequest.predicate = NSPredicate(format: "flightData != nil") do { var fetchedResults: [NSManagedObject] fetchedResults = try viewContext.fetch(fetchRequest) for airplane in fetchedResults { let fdEntity = NSEntityDescription.insertNewObject( forEntityName: "FlightData, into: context ) let flightData = airplane.value(forKey: "flightData") fdEntity.setValue(flightData, forKey: “data”) fdEntity.setValue(airplane, forKey: "aircraft") airplane.setValue(nil, forKey: "flightData") } try context.save() } catch { // Handle any errors that may occur } } }
-
17:41 - Loading the persistent stores with an NSStagedMigrationManager
let migrationStages = [lightweightStage, customStage] let migrationManager = NSStagedMigrationManager(migrationStages) let persistentContainer = NSPersistentContainer( path: "/path/to/store.sqlite", managedObjectModel: myModel ) var storeDescription = persistentContainer?.persistentStoreDescriptions.first storeDescription?.setOption( migrationManager, forKey: NSPersistentStoreStagedMigrationManagerOptionKey ) persistentContainer?.loadPersistentStores { storeDescription, error in if let error = error { // Handle any errors that may occur } }
-
21:01 - Adding a persistent store with NSPersistentStoreDeferredLightweightMigrationOptionKey option
let options = [ NSPersistentStoreDeferredLightweightMigrationOptionKey: true, NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true ] let store = try coordinator.addPersistentStore( ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options )
-
21:17 - Executing deferred migrations
// After using BGProcessingTask to run migration work let metadata = coordinator.metadata(for: store) if (metadata[NSPersistentStoreDeferredLightweightMigrationOptionKey] == true) { coordinator.finishDeferredLightweightMigration() }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。