[toc]
定义
属性包装器是一种给属性附加逻辑的类型。
从结构上看就像是给修饰的属性加了个壳子,对属性的存取都会经过属性包装器设定的逻辑。
逻辑壳子可以是 Class、Struct,意味着它可以拥有自己的属性和方法。
SwiftUI 中的 @State 为例,它的结构如下
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0,*)
@frozen @propertyWrapper public struct State<Value> :DynamicProperty {
public init(wrappedValue value:Value)
public init(initialValue value:Value)
public var wrappedValue: Value {get nonmutating set}
public var projectedValue: Binding<Value> {get}
}
- State 是一个struct
- 被 @propertyWrapper 修饰为属性包装器
- wrappedValue 是属性包装器必要的参数,用来提供包装器逻辑的实际实现。
- projectedValue 是一个可选参数,用来映射自定义的值
- 在任何可以访问到属性的位置都可以使用 .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
注意点:
- 属性包装器不支持局部变量
- 初始化的标签必须是 wrappedValue
- 如果初始化方法中的wrappedValue省略为_,就必须在初始化中赋值
- 必须包含非静态属性 wrappedValue
自定义属性包装器-2
@propertyWrapper struct RangeLimit<A: Comparable> {
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)
- 在正常使用之前,需要把所有的全局存储属性赋值
- 包装器的初始化方法的 wrappedValue 参数,和属性赋值不可以同时存在,
- 如果初始化方法中的wrappedValue省略为_,就必须在初始化中赋值
- 初始化参数可以有多个,但是 wrappedValue 是必须的
- 包装器的属性不执行 didSet 方法
多包装器的并列使用
@propertyWrapper struct ZPrint<A> {
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<String>(value: "C", max: "C", min: "B")
@ZPrint @ZPrint @RangeLimit(min: "B", max: "C") var abcd = "b"
// 日志输出
// ===
// RangeLimit<String>(value: "C", max: "C", min: "B")
// ===
// ZPrint<RangeLimit<String>>(value: ModelTest.RangeLimit<Swift.String>(value: "C", max: "C", min: "B"))
- 执行的顺序是从内到外的,从离属性近的开始执行(即从右到左,由下而上的)
- 包装器的属性不执行 didSet 方法
- 包装器是被嵌套的,ZPrint 的 wrappedValue 实际是 RangeLimit 而不是属性abc
- 也就是多包装器并列只用的本质是,包装器的嵌套
- 最好不要并列使用包装器,可以使用新的包装器,重新包装两个现有包装器,下一节
封装多个包装器
@propertyWrapper struct PrintLimit<A: Comparable> {
private var value: ZPrint<RangeLimit<A>>
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<String>(value: "C", max: "C", min: "A")
// ===
// RangeLimit<String>(value: "C", max: "C", min: "A")
- 执行两遍是因为:
- value 的初始化执行一遍
- wrappedValue 的赋值执行一遍
- 可以避开多次赋值,
网友评论