@State

用途

struct 1.私有的View State,使用当前View维护状态生命周期 2.保持struct不可变性的同时修改内部变量值

class 1.私有的View State,使用当前View维护状态生命周期

使用方式

struct Counter: View {
    @State private var value = 0

    var body: some View {
        Button("Increment: \(value)") {
            value += 1
        }
    }
}

内部实现

struct Counter: View {
    private var _value = State(initialValue: 0)
    private var value: Int {
        get { _value.wrappedValue }
        nonmutating set { _value.wrappedValue = newValue }
    }

    var body: some View {
        Button("Increment: \(value)") {
            value += 1
        }
    }
}

SwiftUI给value state在render tree里面分配内存并赋予initialValue:0,并建立链接使value指向这个内存值 Counter’s body依赖这个内存值,一旦内存值变化会重新构建Counter’s body.

@Observable

用途

1.给Object增加Observable marker protocol 2.追踪Object’s properties的读和写

使用方式

1.使用CounterView管理model生命周期

@Observable final class Model {
    var value = 0
}

struct Counter: View {
    @State private var model = Model()
    var body: some View {
        Button("Increment: \(model.value)") {
            model.value += 1
        }
    }
}

2.外部传递,由外部对象管理生命周期

@Observable final class Model {
    var value = 0
    static let shared = Model()
}

struct Counter: View {
    var model: Model
    var body: some View {
        Button("Increment: \(model.value)") {
            model.value += 1
        }
    }
}

struct ContentView: View {
    var body: some View {
        Counter(model: Model.shared)
    }
}

内部实现

@Observable final class Model {
    var value = 0 {
        get {
            access(keyPath: \.value )
            return _value
        }
        set {
            withMutation(keyPath: \.value ) {
                _value = newValue
            }
        }
    }
    @ObservationIgnored private var _value = 0
    //…
}

//展开access和withMutatio
@Observable final class Model {
    //…
    @ObservationIgnored private let _$observationRegistrar = ObservationRegistrar()
    internal nonisolated func access<Member>(keyPath: KeyPath<Model , Member>) {
        _$observationRegistrar.access(self, keyPath: keyPath)
    }
    internal nonisolated func withMutation<Member, T>(
        keyPath: KeyPath<Model , Member>,
        _ mutation: () throws -> T
    ) rethrows -> T {
        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
    }
}

access和withMutation两个方法会被observationRegistrar调用. registrar负责保持observers订阅的properties,并通知observers这些properties的变化. SwiftUI有个withObservationTracking(_ apply:onChange:)全局函数. 其中apply闭包会立即执行,帮助observation订阅对象. onChange闭包是observer,当observable properties变化时被调用.

apply调用后,object的accessd properties和observer闭包,建立关联性.

类似如下:

withObservationTracking {
    view.body
} onChange: {
    view.needsUpdate()
}

上述代码表示任何view.body内的observable property都通过object’s observation registrar注册.并将property和当前view.body形成关联关系.

@Binding

用途

1.确保app的状态一致性,每个状态都有一个单一源(source of truth) 2.property从外面传递进来

使用方式

struct Counter: View {
    @Binding var value: Int
    var body: some View {
        Button("Increment: \(value)") { value += 1 }
    }
}

struct ContentView: View {
    @State private var value = 0
    var body: some View {
        Counter(value: $value)
    }
}

内部实现

//1.简单实现
struct Counter: View {
    var value: Int
    var setValue: (Int) -> ()
    var body: some View {
        Button("Increment: \(value)") { setValue(value + 1) }
    }
}

struct ContentView: View {
    @State private var value = 0
    var body: some View {
        Counter(value: value, setValue: { value = $0 })
    }
}

//2.使用Binding类型
struct Counter: View {
    var value: Binding<Int>
    var body: some View {
        Button("Increment: \(value.wrappedValue)") {
            value.wrappedValue += 1
        }
    }
}

//使用computed value 简化使用,避免直接调用wrappedValue
struct Counter: View {
    var _value: Binding<Int>
    var value: Int {
        get { _value.wrappedValue }
        set { _value.wrappedValue = newValue }
    }
    init(value: Binding<Int>) {
        self._value = value
    }
    var body: some View {
        Button("Increment: \(value)") { value += 1 }
    }
}

struct ContentView: View {
    @State private var value = 0
    var body: some View {
        Counter(value: Binding(get: { value }, set: { value = $0 }))
    }
}

在ContentView里面set:把 $0和@State private property value关联起来,当Counter里面使用$0改变value值,会导致Counter view被重新构建 前面使用$value,是取用@State property的projectedValue($是取用property wrapper’s projectedValue的语法糖), 该projectedValue实现Binding(get: { value }, set: { value = $0 }),实现如下:

struct ContentView: View {
    private var _value = State(initialValue: 0)
    private var value: Int {
        get { _value.wrappedValue }
        set { _value.wrappedValue = newValue }
    }
    var body: some View {
        Counter(value: _value.projectedValue)
    }
}

@Bindable

用途

1.解决Observable Object没有使用@State warpper,因此没有projectedValue,无法使用$value进行唯一源绑定,如下示例使用Bindable可以直接传递model对象进Counter 2.@Binable 构建了projectedValue, 如下例可以在Counter里面使用$语法糖进行object property的绑定

使用方式

1.使用property wrapper

@Observable final class Model {
    var value = 0
    static let shared = Model()
}

struct Counter: View {
    @Bindable var model: Model
    var body: some View {
        Stepper("\(model.value)", value: $model.value)
    }
}

struct ContentView: View {
    var model = Model.shared
    var body: some View {
        Counter(model: model)
    }
}

2.使用inline方式

struct ContentView: View {
    var model = Model.shared
    var body: some View {
        Stepper("\(model.value)", value: Bindable(model).value)
    }
}

不使用Counter View,直接使用Bindable(model).value建立联接

内部实现

struct Counter: View {
    var _model: Bindable<Model>
    var model: Model { _model.wrappedValue }
    
    init(model: Model) {
        _model = Bindable(wrappedValue: model)
    }
    var body: some View {
        Stepper("\(model.value)", value: _model.projectedValue[dynamicMember: \.value])
    }
}

使用动态成员查找语法projectedValue[dynamicMember: .value],查找需要修改的object property

怎么选择

  1. 优先选择不使用任何property wrapper,如果我们只是传递值给view, view并不需要改变这个值
  2. 关于value,如果view需要改变这个值,view自己管理对象使用@State,由外部传入使用@Binding
  3. 关于object,view自己管理对象使用@State, @Observable, 如果由外部传入使用@Observable
  4. 关于object由外部传入使用@Observable, 还想进行后续使用$进行关联,还需使用@Bindable