普通视图
Runloop的无限循环
Swift 属性包装器
我们来看 The Swift Programming Language (6.2.3) 中的例子。
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
结构 TwelveOrLess 是属性包装器,属性包装器可以是 class、struct 和 enum。属性包装器需要有个属性 wrappedValue,表示被包装的值。TwelveOrLess 的 wrappedValue 属性是计算属性,读写私有的存储属性 number,其 setter 确保 number 小于或等于 12。
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
结构 SmallRectangle 应用包装器 TwelveOrLess 到属性 height 和 width,编译器重写代码为:
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
生成 _height 和 _width 存储属性,存储包装器 TwelveOrLess 的实例。height 和 width 成为计算属性,访问 _height 和 _width 的 wrappedValue。
![]()
编译器还会为 SmallRectangle 生成 memberwise 初始化器,此时生成的初始化器为
init(
height: TwelveOrLess = TwelveOrLess(),
width: TwelveOrLess = TwelveOrLess()
)
参数 height 和 width 的类型为包装器类型 TwelveOrLess,TwelveOrLess 的初始化器为默认初始化器 init()。 如果 TwelveOrLess 增加初始化器 init(wrappedValue: Int),
@propertyWrapper
struct TwelveOrLess {
private var number = 0
init(wrappedValue: Int) {
self.number = min(wrappedValue, 12)
}
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
则 SmallRectangle 的初始化器为
init(
height: Int,
width: Int
)
参数 height 和 width 的类型为原始类型 Int。
如果 TwelveOrLess 增加初始化器 init(),
@propertyWrapper
struct TwelveOrLess {
private var number = 0
init() {
number = 1
}
init(wrappedValue: Int) {
self.number = min(wrappedValue, 12)
}
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
则 SmallRectangle 的初始化器为
init(
height: TwelveOrLess = TwelveOrLess(),
width: TwelveOrLess = TwelveOrLess()
)
此时想让 SmallRectangle 的 memberwise 初始化器参数类型为原始类型 Int,需要修改为
struct SmallRectangle {
@TwelveOrLess var height: Int = 1
@TwelveOrLess var width: Int = 1
}
编译器生成的代码为
struct SmallRectangle {
private var _height = TwelveOrLess(wrappedValue: 1)
private var _width = TwelveOrLess(wrappedValue: 1)
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
init(height: Int, width: Int) {
self.height = height
self.width = width
}
}
总结:对一个 struct 的某个属性应用包装器,要使 memberwise 初始化器对应参数类型为原始类型,需要如下条件之一,
- 属性包装器有初始化器 init(wrappedValue:),并且没有 init()
- 属性有初始值,像 @TwelveOrLess var height: Int = 1
否则,memberwise 初始化器参数类型为包装器类型。
除了被包装的值,属性包装器可以通过定义一个 projected value 暴露额外的功能。
@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
上面代码中,SmallNumber 结构增加了一个属性 projectedValue,用来记录包装器是否调整了被包装的值。
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
print(someStructure.$someNumber)
// 打印 false
someStructure.someNumber = 55
print(someStructure.$someNumber)
// 打印 true
通过在被包装的属性名前增加 $ 来访问包装器的 projectedValue。