我有以下视图模型:
class ViewModel: ObservableObject {
@Published var brightnessValue: Double = 0.0
@Published var saturationValue: Double = 0.0
@Published var contrastValue: Double = 0.0
}
在 UI 层,我有 3 个滑块,实现为 SwiftUI 视图,每个都绑定到来自上述视图模型的
Double
。每当滑块更改属性时,所有 3 个滑块都会重新绘制。这是因为对 Published
中的 ObservableObject
属性的更改会重绘所有引用它的视图。
这就是 SwiftUI 视图的样子:
struct RootView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
AdjustmentSlider(name: "brightness", sliderValue: $viewModel.brightnessValue)
AdjustmentSlider(name: "saturation", sliderValue: $viewModel.saturationValue)
AdjustmentSlider(name: "contrast", sliderValue: $viewModel.contrastValue)
}
.background(.random)
}
}
struct AdjustmentSlider: View {
let name: String
@Binding var sliderValue: Double
var body: some View {
Slider(value: $sliderValue)
.background(.randomColor) // I use this to visualize the redrawing.
}
}
最后,这是整体(示例)应用程序设计:
这将是一个非常复杂的应用程序的一部分,有几十个滑块,所以我担心随着视图模型变得更加复杂,这种不断的重绘可能会成为一个重要的性能瓶颈。
有什么方法可以为 SwiftUI 设计更 高效 视图模型(即更少的重绘)?而且,更广泛地说,在使用大约 iOS 16 的 SwiftUI 时,这甚至是一个有效的问题吗?
在 SwiftUI 中,
View
结构是一个视图模型并保存视图数据。所以使用一个对象代替它已经是低效的并且会导致一致性错误。我们应该使用 let
来表示不变的数据,@State
来表示确实发生变化的数据,以及 @State var
结构来将相关的变量组合在一起,您可以使用 mutating func
来表示您想要的任何逻辑独立测试。使用 computed var 在将数据传递到 body
中的子视图初始化时转换数据,例如MySubView(myComputedVar)
,这就是 View
结构层次结构的要点——在向下的过程中从丰富的模型类型转换为简单类型。在你的情况下应该是这样的:
struct MyColor {
var brightnessValue: Double = 0.0
var saturationValue: Double = 0.0
var contrastValue: Double = 0.0
}
struct RootView: View {
@State var color = MyColor()
var body: some View {
VStack {
AdjustmentSlider(name: "brightness", sliderValue: $color.brightnessValue)
AdjustmentSlider(name: "saturation", sliderValue: $color.saturationValue)
AdjustmentSlider(name: "contrast", sliderValue: $color.contrastValue)
}
.background(.random)
}
}
View 结构不“绘制”任何东西。这些是超快速的轻量级值类型,如
int x = 3
。生成 View
结构基本上可以忽略不计,因为它存储在内存堆栈而不是堆上。 SwiftUI 重新计算 View
层次结构的部分,它检测到数据依赖性更改(它记录视图调用 @State
getter 的内容),它与上次比较层次结构,并使用其结果来添加/删除/更新 UIViewController
和 UIView
对象自动为我们服务。所以基本上我们不需要担心 View init 和 body 经常被调用,但是我们需要有效地使用像 @State
和 @Binding
这样的值类型并且尽量不要使用对象并且永远不要在 View 结构中初始化对象,由于该结构不断地被重新创建,它不能挂在一个对象上——因此这样做本质上是一个内存泄漏,并且会不断地做无意义的堆分配来减慢它的速度。
为了使 SwiftUI 更高效,只需将所有内容分解为尽可能小的视图,并且
body
仅使用在该视图中定义的 lets/vars。这被称为具有“更严格范围内的失效”(SwiftUI WWDC 2020 中的数据要点 @ 12:21)