属性包装器(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)
        }
    }
}
  1. init(initialValue:) 的存在使得可以直接给 brain 赋值,将 CalculatorBrain 传递给 brain。属性包装器中可以为 init 方法添加更多参数。initialValue 这个参数名较特殊:当它出现在 init 方法的第一个参数位置时,编译器允许在声明时直接为 @State var brain 赋值。

  2. 访问 brain 时,变量暴露出 CalculatorBrain 的行为和属性。对 brain 赋值看起来和普通变量赋值没有区别,但实际触发的是属性包装中的 wrappedValue。@State 声明在底层将 brain 属性"包装"到 State 中,并保留外界使用者通过 CalculatorBrain 接口操作的可能性。

  3. 使用美元符号前缀($)访问 brain 实际访问的是 projectedValue 属性。在 State 中,这个属性返回 Binding 类型值,通过遵守 BindingConvertible,State 暴露了修改其内部存储的方法,这也是 Binding 具有引用语义的原因。

ObservableObject 和 @ObjectBinding Link to heading

ObservableObject 协议要求实现类型是 class,只需实现 objectWillChange 属性。数据将要改变时,这个属性向外"广播",订阅者(通常是 View 相关逻辑)收到通知后对 View 进行刷新。

创建 ObservableObject 后,在 View 中使用时需要声明为 @ObservedObject。这个属性包装器负责订阅 objectWillChange 这个"广播",将具体管理数据的 ObservableObject 和当前 View 关联起来。