Swift 属性包装器@propertyWrapper

[toc]

定义

属性包装器是一种给属性附加逻辑的类型。

从结构上看就像是给修饰的属性加了个壳子,对属性的存取都会经过属性包装器设定的逻辑。

逻辑壳子可以是 Class、Struct,意味着它可以拥有自己的属性和方法。

SwiftUI 中的 @State 为例,它的结构如下

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0,*)
@frozen @propertyWrapper public struct State :DynamicProperty {
    public init(wrappedValue value:Value)
    public init(initialValue value:Value)
    public var wrappedValue: Value {get nonmutating set}
    public var projectedValue: Binding {get}
}
  1. State 是一个struct
  2. 被 @propertyWrapper 修饰为属性包装器
  3. wrappedValue 是属性包装器必要的参数,用来提供包装器逻辑的实际实现。
  4. projectedValue 是一个可选参数,用来映射自定义的值
  5. 在任何可以访问到属性的位置都可以使用 .XXX 实际上获取的是projectedValue

自定义属性包装器-1


@propertyWrapper struct Upper {
    private var value = ""
    var wrappedValue: String {
        get {
            Log.Info(message: "value")
            return value
        }
        set {
            Log.Info(message: "value")
            value = newValue.uppercased()
        }
    }
    
    init(wrappedValue: String) {
        Log.Info(message: wrappedValue)
        self.wrappedValue = wrappedValue
    }
}

@Upper var abc = "avc"
print(abc)
// 输出:AVC

注意点:

  1. 属性包装器不支持局部变量
  2. 初始化的标签必须是 wrappedValue
  3. 如果初始化方法中的wrappedValue省略为_,就必须在初始化中赋值
  4. 必须包含非静态属性 wrappedValue

自定义属性包装器-2

@propertyWrapper struct RangeLimit {
    private var value: A
    private var max: A
    private var min: A
    var wrappedValue: A {
        get {
            Log.Info(message: "get value")
            return value
        }
        set {
            Log.Info(message: "set valse")
            value = min < newValue ? max > newValue ? newValue : max : min
        }
    }
    
    init(wrappedValue: A, min: A, max: A) {
        Log.Info(message: wrappedValue)
        self.min = min
        self.max = max
        self.value = wrappedValue
        self.wrappedValue = wrappedValue
    }
}

@RangeLimit(wrappedValue: "A", min: "B", max: "C") var abc
@RangeLimit(min: "B", max: "C") var abc = "b"

print(abc)
abc = "D"
print(abc)

  1. 在正常使用之前,需要把所有的全局存储属性赋值
  2. 包装器的初始化方法的 wrappedValue 参数,和属性赋值不可以同时存在,
  3. 如果初始化方法中的wrappedValue省略为_,就必须在初始化中赋值
  4. 初始化参数可以有多个,但是 wrappedValue 是必须的
  5. 包装器的属性不执行 didSet 方法

多包装器的并列使用


@propertyWrapper struct ZPrint {
    private var value: A
    var wrappedValue: A {
        get {
            value
        }
        set {
            Log.Info(message: "===")
            print(newValue)
            value = newValue
        }
    }
    init(wrappedValue: A) {
        Log.Info(message: wrappedValue)
        self.value = wrappedValue
        self.wrappedValue = wrappedValue
    }
}

// 另一个包装器使用上面的
@ZPrint @RangeLimit(min: "B", max: "C") var abc = "b"
// 日志输出
//  ===
// RangeLimit(value: "C", max: "C", min: "B")

@ZPrint @ZPrint @RangeLimit(min: "B", max: "C") var abcd = "b"
// 日志输出
// ===
// RangeLimit(value: "C", max: "C", min: "B")
// ===
// ZPrint>(value: ModelTest.RangeLimit(value: "C", max: "C", min: "B"))

  1. 执行的顺序是从内到外的,从离属性近的开始执行(即从右到左,由下而上的)
  2. 包装器的属性不执行 didSet 方法
  3. 包装器是被嵌套的,ZPrint 的 wrappedValue 实际是 RangeLimit 而不是属性abc
    1. 也就是多包装器并列只用的本质是,包装器的嵌套
    2. 最好不要并列使用包装器,可以使用新的包装器,重新包装两个现有包装器,下一节

封装多个包装器


@propertyWrapper struct PrintLimit {
    private var value: ZPrint>
    
    var wrappedValue: A {
        get {
            return value.wrappedValue.wrappedValue
        }
        set {
            value.wrappedValue.wrappedValue = newValue
        }
    }
    
    init(wrappedValue: A, min: A, max: A) {
        self.value = ZPrint(wrappedValue: RangeLimit(wrappedValue: wrappedValue, min: min, max: max))
        self.wrappedValue = wrappedValue
    }
}

// ===
// RangeLimit(value: "C", max: "C", min: "A")
// ===
// RangeLimit(value: "C", max: "C", min: "A")

  1. 执行两遍是因为:
    1. value 的初始化执行一遍
    2. wrappedValue 的赋值执行一遍
    3. 可以避开多次赋值,

你可能感兴趣的:(Swift 属性包装器@propertyWrapper)