大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 SwiftData 为你的架构建模
了解如何将架构宏和迁移计划与 SwiftData 结合使用,为你的 App 构建更复杂的功能。我们将向你展示如何使用 @Attribute 和 @Relationship 选项来微调持久性。了解如何使用 @Transient 从数据模型中排除属性,并轻松从一个版本的架构迁移到下一版本。 为了充分了解本次讲座,我们建议首先观看 WWDC23 的“认识 SwiftData”和“使用 SwiftData 构建 App”。
章节
- 0:00 - Intro
- 1:41 - Utilizing schema macros
- 5:30 - Evolving schemas
- 8:56 - Wrap-up
资源
相关视频
WWDC23
-
下载
♪ ♪
Rishi:大家好 我是 Rishi Verma 本讲座将向你介绍如何编写模型代码 来为 SwiftData 构建架构 首先 我会向你介绍 如何充分利用架构宏 以及随着 App 的更改 如何使用架构迁移更新架构 在我们开始之前 请观看“认识 SwiftData” 以及“使用 SwiftData 编译 App” 因为本讲座的内容是 基于这些视频中引入的概念 SwiftData 是一个强大的框架 可用于数据建模和数据管理 以及增强现代 Swift App 与 SwiftUI 一样 该框架以代码为重点 没有外部文件格式 并且使用 Swift 新的宏系统 来创造无缝 API 体验 最近 我正在开发 SampleTrips App 该 App 可以让用户 对未来的旅行进行规划 每个旅行在创建时 都会包含名称、目的地 以及开始和结束日期 同时每个旅行还会包含愿望清单项目 以及旅客未来驻留地之间的关系 添加 SwiftData 十分简单 你只需要添加导入 并使用 @Model 修饰 Trip 即可 这样就可以了 @Model 宏可以让 Trip 类 遵循 PersistentModel 并生成一个描述性架构 同时 定义模型的代码现在是 App 架构的数据源 虽然 Trip 模型中的 默认行为还不错 但我觉得还可以再微调一下 SwiftData 中的架构宏 让我能自定义持久性的体验行为 并使其完美适配我的 App 在我发布 使用原始架构的 App 时 我无法确保 每个旅行的名字都是唯一的 因而具有相同名称的旅行间 便会产生冲突 现在我就来解决这些冲突 通过使用 @Attribute 架构宏以及 unique 选项 该问题便可以得到解决 SwiftData 会为我的 Trip 模型 生成一个架构 并确保我保存到持久后端的旅行 都具有独特的名字 如果出现使用此前同一名称的旅行 持久后端便会更新到最新值 这个过程称为更新插入 更新插入会从插入开始 如果插入和现有数据发生冲突 其便会成为最新内容 并更新现有数据的属性 此外 我也可以将唯一约束 应用到其他属性上 只要这些属性的原始数据类型为 数字、字符串或 UUID 等即可 或者 我甚至可以修饰对一关系 所以我的架构还需要进一步的改进 接下来 我想从最初指定的 start_date 和 end_date 中 删除这些让人讨厌的下划线 但如果我直接对变量重新命名 这些名称就会成为 生成架构中的新属性 但我并不想 SwiftData 创建这些新属性 而是想让现有数据保留原样 为了实现这一点 我只需使用 @Attribute 并指定 originalName: parameter 将初始名称投射到属性名称即可 通过从初始名称进行映射 我就可以避免数据丢失 同时 这还可以确保架构的更新是向 SampleTrips App 下一个版本的简单迁移 此外 @Attribute 宏 还具有其他功能 包括将大型数据存储在外部 以及为可转换的对象提供支持等
我的旅行改进效果还不错 但我还想对其中的关系进行处理 在我向 Trip 添加新的 BucketListItem 和 LivingAccommodation 后 SwiftData 就会隐式地发现 模型的反向关系 并为我进行设置 这种隐式的反向关系 并不需要任何注释 自身便可执行操作 隐式反向关系使用默认删除规则 即当 Trip 被删除时 这会使 BucketListItem 和 LivingAccommodation 失效 但是我希望在删除 Trip 时 可以一同删除 BucketListItem 和 LivingAccommodation 为了实现这一点 我只需要添加 @Relationship 宏 并使用级联删除规则即可 现在 当我删除旅行 其也会对这些关系进行删除 此外 @Relationship 宏 还具有其他功能 包括 originalName 修饰符 以及在对多关系上 指定最小计数和最大计数 现在 SampleTrips App 得到进一步完善 但我还想再对其进行一项更新 我想添加一个功能 来跟踪我查看旅行的次数 这样我就可以估计 我对度假的期待程度了 我简直等不及了 但是我不希望 SwiftData 持久化 该查看次数 使用 @Transient 宏便可以 轻松实现这一点 我只需要使用 @Transient 对属性进行修饰 那么该特定属性就不会被持久化了 就是这么简单 @Transient 宏可以帮助你 避免持久化不必要的数据 但你需要确保为临时属性 提供默认值 这样才可以确保从 SwiftData 中 获取此类属性时 其具备逻辑值 想要了解更多有关使用 这些架构宏的信息 请查看 SwiftData 文档 在我不断调整持久性的体验过程中 SampleTrips App 也得到了多次更新 接下来 我需要确保我的 App 可以处理这种版本之间的更新 如果你对架构进行了更改 例如添加或删除属性 便需要对数据进行迁移 这些迁移可能会很麻烦 但是 SwiftData 可以让这一过程变得轻松简便 VersionedSchema 和 SchemaMigrationPlan 可以提供帮助 每当你准备发布对 SwiftData 模型 进行修改的 App 新版本时 你都需要定义一个 VersionedSchema 来对此前发布的架构进行封装 并且 你需要将架构的每个独立版本 都定义为一个 VersionedSchema 以告知 SwiftData 版本间 发生了何种更改 接着 你需要使用 VersionedSchemas 的 总排序 来创建一个 SchemaMigrationPlan 来让 SwiftData 按顺序执行所需的迁移操作 当你在迁移计划中 排列好有序的架构后 你就可以开始定义各个迁移阶段 你可以使用两种 不同类型的迁移阶段 第一种是轻量级迁移阶段 轻量级阶段无需任何额外的代码 便可迁移用于下一版本 App 的 现有数据 例如 向数据属性添加 originalName 以及指定关系的删除规则等修改 都适用于轻量级迁移 但是 让旅行名称变得唯一 并不适用于轻量级迁移 为此 我需要为该更改 创建一个自定义阶段 让我在名称变得唯一前 对旅行进行去重操作 首先 我会从第一个版本中 获取初始架构 并将其封装进 VersionedSchema 中 我将该版本化模式命名为 SampleTripsSchemaV1 并且 每个模式化版本都列出了 其所定义的模型类 我在版本 2 的架构中对旅行名称 添加了唯一性约束 并创建了另一个版本化架构 该架构也封装了 我对 Trip 模型类做的更改 在版本 3 架构中 我也进行了 相同的操作 并用于捕获 开始和结束日期的名称更改 由于我就拥有全部的 VersionedSchemas 接下来我会构建一个 SchemaMigrationPlan 来描述如何处理版本之间的迁移 这个过程相当简单 我只需要提供 App 架构的总排序即可 然后 我便需要注释迁移的类型 是轻量级的还是自定义的 从 V1 到 V2 我需要设置一个自定义阶段 以便在数据迁移之前 在这其中执行操作 在 willMigrate 闭包中 我会在迁移发生前 对旅行进行去重操作 SwiftData 可以检测迁移从 V1 到 V2 发生的时间 并会为我执行该闭包 由于 originalName 另一个迁移是轻量级的 所以我也会在该迁移中 添加自定义阶段 现在 我便对迁移计划中的所有细节 完成了定义 我们来执行一下该迁移 在这里 我使用当前架构和 migrationPlan 来设置 ModelContainer 这样就可以了 接着 我的用户就可以 从任意版本升级到最新版本 并且我也可以确保数据得到保留 我非常期待使用 SampleTrips App 来规划即将到来的假期 使用架构宏为架构传递附加元数据 并随着 App 的升级捕获 VersionedSchema 的更改 接着 你的 App 便可以从之前 任意版本中实现迁移 欢迎观看其他讲座 我们期待看到你使用 SwiftData 创造精彩内容 这会让我们感到万分荣幸
-
-
0:56 - Original Trip model
import SwiftUI import SwiftData @Model final class Trip { var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
1:50 - Adding a unique attribute
@Model final class Trip { @Attribute(.unique) var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
2:48 - Specifying original property names
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
4:00 - Cascading delete rule
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date @Relationship(.cascade) var bucketList: [BucketListItem]? = [] @Relationship(.cascade) var livingAccommodation: LivingAccommodation? }
-
4:54 - Transient properties
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date @Relationship(.cascade) var bucketList: [BucketListItem]? = [] @Relationship(.cascade) var livingAccommodation: LivingAccommodation? @Transient var tripViews: Int = 0 }
-
7:12 - Defining versioned schemas
enum SampleTripsSchemaV1: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... } enum SampleTripsSchemaV2: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { @Attribute(.unique) var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... } enum SampleTripsSchemaV3: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... }
-
7:49 - Implementing a SchemaMigrationPlan
enum SampleTripsMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [SampleTripsSchemaV1.self, SampleTripsSchemaV2.self, SampleTripsSchemaV3.self] } static var stages: [MigrationStage] { [migrateV1toV2, migrateV2toV3] } static let migrateV1toV2 = MigrationStage.custom( fromVersion: SampleTripsSchemaV1.self, toVersion: SampleTripsSchemaV2.self, willMigrate: { context in let trips = try? context.fetch(FetchDescriptor<SampleTripsSchemaV1.Trip>()) // De-duplicate Trip instances here... try? context.save() }, didMigrate: nil ) static let migrateV2toV3 = MigrationStage.lightweight( fromVersion: SampleTripsSchemaV2.self, toVersion: SampleTripsSchemaV3.self ) }
-
8:40 - Configuring the migration plan
struct TripsApp: App { let container = ModelContainer( for: Trip.self, migrationPlan: SampleTripsMigrationPlan.self ) var body: some Scene { WindowGroup { ContentView() } .modelContainer(container) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。