属性包装器(Property Wrapper)是 SwiftUI 状态管理的基础机制。
重点:
- 属性包装器的工作原理
- 常见包装器的实现机制
- wrappedValue 和 projectedValue 作用
- ObservableObject 与数据响应
在 Swift 中,这一特性的正式名称是属性包装(Property Wrapper)。@State、@Binding、@ObjectBinding 和 @EnvironmentObject 都是被 @propertyWrapper 修饰的 struct 类型。以 State 为例,SwiftUI 中 State 定义的关键部分如下:
@propertyWrapper
public struct State<Value> :
DynamicViewProperty, BindingConvertible
{
public init(initialValue value: Value)
public var value: Value { get nonmutating set }
public var wrappedValue: Value { get nonmutating set }
public var projectedValue: Binding<Value> { get }
}
init(initialValue:)、wrappedValue 和 projectedValue 构成了属性包装器最重要的部分。在 @State 的实际使用中:
struct ContentView : View {
@State
private var brain: CalculatorBrain = .left("0")
var body: some View {
VStack(spacing: 12) {
Spacer()
Text(brain.output)
CalculatorButtonPad(brain: $brain)
}
}
}
-
init(initialValue:) 的存在使得可以直接给 brain 赋值,将 CalculatorBrain 传递给 brain。属性包装器中可以为 init 方法添加更多参数。initialValue 这个参数名较特殊:当它出现在 init 方法的第一个参数位置时,编译器允许在声明时直接为 @State var brain 赋值。
-
访问 brain 时,变量暴露出 CalculatorBrain 的行为和属性。对 brain 赋值看起来和普通变量赋值没有区别,但实际触发的是属性包装中的 wrappedValue。@State 声明在底层将 brain 属性"包装"到 State 中,并保留外界使用者通过 CalculatorBrain 接口操作的可能性。
-
使用美元符号前缀($)访问 brain 实际访问的是 projectedValue 属性。在 State 中,这个属性返回 Binding 类型值,通过遵守 BindingConvertible,State 暴露了修改其内部存储的方法,这也是 Binding 具有引用语义的原因。
ObservableObject 和 @ObjectBinding Link to heading
ObservableObject 协议要求实现类型是 class,只需实现 objectWillChange 属性。数据将要改变时,这个属性向外"广播",订阅者(通常是 View 相关逻辑)收到通知后对 View 进行刷新。
创建 ObservableObject 后,在 View 中使用时需要声明为 @ObservedObject。这个属性包装器负责订阅 objectWillChange 这个"广播",将具体管理数据的 ObservableObject 和当前 View 关联起来。