大多数浏览器和
Developer App 均支持流媒体播放。
-
探索 SwiftUI 中的观察
使用观察简化你的 SwiftUI 数据模型。我们将分享 Observable 宏如何帮助你简化模型并提高 App 的性能。了解观察,了解宏的基础知识,并了解如何从 ObservableObject 迁移到 Observable。
章节
- 1:03 - What is Observation?
- 4:23 - SwiftUI property wrappers
- 7:34 - Advanced uses
- 10:27 - ObservableObject
资源
相关视频
WWDC23
-
下载
♪ ♪
Philippe:大家好 我是 Philippe 我很高兴向大家介绍 Swift 中一个神奇的新功能: 观察 该功能让你可以使用 标准 Swift 语法定义模型 并且使用这些类型 来使 UI 响应模型的变化 这使得在 SwiftUI 中 开发变得连贯且直观 今天我们的讲座 将涵盖以下几个主题: 观察功能的概述; 在何时使用 SwiftUI 的 属性包装器的一些便利规则; 然后我们将介绍一些更高级的 Observable 用法; 最后 我们将通过 一些示例演示如何更新代码 从使用 ObservableObject 到新的 @Observable 宏
观察是 Swift 中 用于跟踪属性变化的新功能 它适用于普通的 Swift 类型 并通过宏的神奇能力 对它们进行转换 我们经常编写数据模型类型 并且它们有许多最终我们希望在 SwiftUI 中使用的属性 如果我告诉你只需 添加 @Observable 就可以使 UI 响应数据模型的变化 你会相信吗? Swift 5.9 中的新功能 使你能够创建比以往更简单的模型 这使用了 Swift 中 新的宏系统 “@Observable”告诉 Swift 编译器将你的代码 转换为一个扩展形式 这能使该类型能够被观察 你可以使用 Observable 类型 来驱动你的 SwiftUI 视图 令人惊奇的是 它们无需任何属性包装器即可工作 我有一些来自我们 甜甜圈餐车 App 的精彩示例 让我们开始吧 在这里 我们有一个 简约视图显示甜甜圈 SwiftUI 知道模型 在执行 body 调用时 访问特定属性 在这种情况下 它可以检测到执行 甜甜圈菜单视图的 body 时 访问了属性“donuts” 当执行 body 时 SwiftUI 跟踪所有从 “Observable” 类型使用的属性访问 然后 它利用这些跟踪信息来确定 在这些特定实例上的 任何这些属性的下一个变化 将发生在何时 在这里 如果我们通过点击添加 甜甜圈按钮更改 donuts 数组 这会使甜甜圈菜单视图无效 并相应地更新 UI 有趣的是 举例来说 如果添加了一个订单 该视图不会无效 因为该属性不属于被跟踪的属性 它是在执行视图的 body 时 确定的 接下来 让我们来看看 当你使用计算属性时会发生什么 添加计算属性 遵循与之前相同的规则 当使用的属性 发生变化时 UI 将更新 在新添加的内容中 调用了 模型的 orderCount 它访问了 orders 属性 因此那意味着 在该示例中 如果订单发生变化 文本将会被更新 那是因为 orderCount 访问了 order 的属性 使用“@Observable”宏 扩展你的类型 使它们能够支持观察 这使得 SwiftUI 跟踪对这些属性的访问 并观察下一个属性何时会发生变化 像这样跟踪这些信息使你的 UI 只在这些特定属性发生变化时 重新计算视图的 body 我们从中看到了 一些非常出色的性能改进 如果你想深入了解宏 请务必查看讲座 “编写 Swift 宏”和 “Swift 宏中的拓展” 通过 Observable SwiftUI 的 属性包装器比以往更加简单 State、Environment 和 Bindable 是与 SwiftUI 一起使用的 三种主要属性包装器 我们已经介绍了 不需要任何属性包装器 即可与 SwiftUI 中的 Observable 类型进行交互的情况 现在让我们 深入探讨需要属性包装器的情况 首先是 @State 当视图需 要在模型中存储自己的状态时 就会使用 @State 属性 在此 我们将 Observable 的模型对象 Donut 在一个表单中进行展示 当表单被展示时 donutToAdd 状态变量 被用于绑定可编辑字段的值 “donutToAdd”属性由 包含它的视图的生命周期管理 接下来是 @Environment Environment 允许值 作为全局可访问的值传播 这样可以在多个地方共享值 Observable 类型 在这里表现得非常出色 因为它们的更新基于访问 在调用 餐车菜单视图的 body 时 访问了账户对象的 userName 属性 因此当 userName 发生变化 菜单视图会更新 最新的属性包装器是 @Bindable Bindable 属性包装器 非常轻量级 它只允许从该类型创建绑定 从可绑定包装的 属性中获取绑定非常简单 只需使用 $ 语法 来获取对该属性的绑定 最常见的情况是 与 Observable 类型绑定 对于 donut 视图 我们使用 Text 显示名称 但实际上我们希望能够编辑该名称 因此 我们可以使用 TextField 而不是 Text TextField 接受一个绑定 它从绑定中读取 以填充 TextField 的值 但用户更改值时也会写回到绑定中 要对 donut 进行绑定 我们只需要在 donut 属性上 使用“@Bindable”属性包装器 属性包装器的注解允许我们使用 “$donut.name”语法 并在使用时创建一个绑定 总结属性包装器 使用 SwiftUI 中的 Observable 模型只需回答三个问题 这个模型需要 成为视图本身的状态吗? 如果是的话 请使用“@State” 这个模型需要成为 App 的全局环境的一部分吗? 如果是的话 请使用“@Environment” 这个模型只需要绑定吗? 如果是的话 请使用新的“@Bindable” 如果以上问题都回答为否 只需将模型作为视图的属性使用 到目前为止 我们已经涵盖了 起初存储在模型中的属性 Observable 可以做更多的事情 因为 SwiftUI 跟踪 对实例字段的访问 这意味着你能使用数组、可选类型 或者其他任何包含 Observable 模型的类型 Donut 列表视图 具有一组 donut 模型 每个模型本身 都是“@Observable” 当这些甜甜圈的名称中的 任何一个发生变化时 SwiftUI 检测到 对特定实例上该属性的访问 并跟踪它以了解何时使视图无效 因此在此示例中 当通过随机按钮更改甜甜圈名称时 视图相应地会进行更新 这使你可以根据需要构建模型 你可以拥有被观察模型的数组 或者甚至是包含其他 Observable 模型的模型类型 Observable 的通用规则是 如果使用的属性发生更改 视图将会进行更新 有一种情况下该规则并不完全适用 如果计算属性 没有任何存储属性组成 则需要执行两个额外步骤 使其与观察兼容 只有当将要观察的属性不是通过 Observable 类型 存储的属性组合来改变时 才需要执行这些操作 在这种情况下 只需当访问该属性和 该属性发生更改时告诉观察 这就是观察 通常合成属性访问的方式 除了这里 我们手动重写了 这些自定义访问点 以便能够读取和存储 非 Observable 位置的名称 大多数情况下 不需要手动进行这种操作 因为大多数情况下 有关模型的属性 是由其他存储属性组成的 但在需要高级功能的罕见情况下 观察足够灵活且易于你自己操作 通过对这些属性的访问 跟踪 Observable 类型 SwiftUI 可以识别组合中的更改 这意味着如果 计算属性是由其他存储属性组成的 那么观察将正常工作 然而 在少数情况下 这个规则并不完全适用 你可以直接使用观察 来手动添加这些调用 以标记访问和修改 之前在餐车 App 中 我们使用了 ObservableObject 来实现 一些与新的 @Observable 宏相似的功能 如果今天你有一个 App 使用 SwiftUI 你可能处于非常相似的情况 Observable 宏 可以简化你的代码 而且很有可能 你也会看到相当大的性能提升 在改变之前 FoodTruckModel 类型 具有 ObservableObject conformance 并且还有一些被标记为 @Published 属性包装器的属性 转换到 @Observable 宏非常容易 我们只需要删除 ObservableObject conformance 删除“@Published” 并使用“@Observable”宏进行标记 在视图方面 有许多 “@ObservedObject”和 “@EnvironmentObject”属性包装器 在所有 “@ObservedObject”包装器的情况下 要么消失要么只需要绑定 并改为新的“@Bindable” “@EnvironmentObject” 包装器变换成了“@Environment” 从 ObservableObject 改为新的“@Observable”宏 主要只是删除注释 或简化为三个主要的属性包装器: @State @Environment 和 @Bindable 因为需要考虑的选项更少 这使得编写新功能更易于理解 观察具有恰到好处的神奇之处 它使你能够轻松入门 并允许你直接使用 @Observable 宏 来处理你的数据模型 当你需要时 它允许你编写高级用例的手动版本 对于新的开发 使用 Observable 是最简单的入门方式 对于现有 App 在添加新功能时 使用 Observable 可以 简化你的模型并提高性能 我建议你尝试一下 亲自体验它的神奇之处 ♪ ♪
-
-
1:26 - Using @Observable
@Observable class FoodTruckModel { var orders: [Order] = [] var donuts = Donut.all }
-
2:12 - SwiftUI property tracking
@Observable class FoodTruckModel { var orders: [Order] = [] var donuts = Donut.all } struct DonutMenu: View { let model: FoodTruckModel var body: some View { List { Section("Donuts") { ForEach(model.donuts) { donut in Text(donut.name) } Button("Add new donut") { model.addDonut() } } } } }
-
3:12 - SwiftUI computed property tracking
@Observable class FoodTruckModel { var orders: [Order] = [] var donuts = Donut.all var orderCount: Int { orders.count } } struct DonutMenu: View { let model: FoodTruckModel var body: some View { List { Section("Donuts") { ForEach(model.donuts) { donut in Text(donut.name) } Button("Add new donut") { model.addDonut() } } Section("Orders") { LabeledContent("Count", value: "\(model.orderCount)") } } } }
-
4:41 - Using @State
struct DonutListView: View { var donutList: DonutList @State private var donutToAdd: Donut? var body: some View { List(donutList.donuts) { DonutView(donut: $0) } Button("Add Donut") { donutToAdd = Donut() } .sheet(item: $donutToAdd) { TextField("Name", text: $donutToAdd.name) Button("Save") { donutList.donuts.append(donutToAdd) donutToAdd = nil } Button("Cancel") { donutToAdd = nil } } } }
-
5:14 - Using @Environment
@Observable class Account { var userName: String? } struct FoodTruckMenuView : View { @Environment(Account.self) var account var body: some View { if let name = account.userName { HStack { Text(name); Button("Log out") { account.logOut() } } } else { Button("Login") { account.showLogin() } } } }
-
6:27 - Using @Bindable
@Observable class Donut { var name: String } struct DonutView: View { @Bindable var donut: Donut var body: some View { TextField("Name", text: $donut.name) } }
-
7:53 - Storing @Observable types in Array
@Observable class Donut { var name: String } struct DonutList: View { var donuts: [Donut] var body: some View { List(donuts) { donut in HStack { Text(donut.name) Spacer() Button("Randomize") { donut.name = randomName() } } } } }
-
9:18 - Manual Observation
@Observable class Donut { var name: String { get { access(keyPath: \.name) return someNonObservableLocation.name } set { withMutation(keyPath: \.name) { someNonObservableLocation.name = newValue } } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。