我们来看 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。由于存储属性 number 有默认值,编译器生成 default 初始化器 init()。
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。
属性 height 和 width 未指定初始值(此时也不能指定初始值),编译器使用 init() 初始化器来设置包装器,编译器还会为 SmallRectangle 生成 default 初始化器和 memberwise 初始化器。
init() {
}
init(
height: TwelveOrLess = TwelveOrLess(),
width: TwelveOrLess = TwelveOrLess()
)
memberwise 初始化器的参数 height 和 width 为包装器类型 TwelveOrLess。
如果 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) }
}
}
TwelveOrLess 不会生成 default 初始化器,SmallRectangle 的属性 height 和 width 未指定初始值(此时可以指定初始值),编译器未能使用 init() 初始化器来设置包装器,重写代码为
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 }
}
init(height: Int, width: Int) {
_height = TwelveOrLess(wrappedValue: height)
_width = TwelveOrLess(wrappedValue: width)
}
}
参数 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 的属性 height 和 width 未指定初始值(此时可以指定初始值),编译器使用 init() 初始化器来设置包装器。编译器重写 SmallRectangle 为
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 }
}
init() {
}
init(height: TwelveOrLess = TwelveOrLess(), width: TwelveOrLess = TwelveOrLess() {
_height = height
_width = width
}
}
memberwise 初始化器的参数 height 和 width 为包装器类型 TwelveOrLess。
由于包装器 TwelveOrLess 有初始化器 init(wrappedValue: Int),SmallRectangle 的属性 height 和 width 可以指定初始值,下面我们指定初始值。
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() {
}
init(height: Int, width: Int) {
_height = TwelveOrLess(wrappedValue: height)
self.width = TwelveOrLess(wrappedValue: width)
}
}
memberwise 初始化器的参数 height 和 width 为原始类型 Int。
总结:对一个 struct 的某个属性应用包装器,以下情况 memberwise 初始化器对应参数类型为原始类型,
- 属性指定初始值(包装器有初始化器 init(wrappedValue:) 才能指定),像 @TwelveOrLess var height: Int = 1
- 属性未指定初始值,包装器有初始化器 init(wrappedValue:),并且没有 init()
其它情况,memberwise 初始化器参数类型为包装器类型。
根据上面的结论,我们指定 height 有初始值,width 没有初始值,看看 memberwise 初始化器参数类型。
struct SmallRectangle {
@TwelveOrLess var height: Int = 1
@TwelveOrLess var width: Int
}
height 属性指定了初始值,对应的参数类型为原始类型 Int,width 未指定初始值,但是包装器有 init(),因此对应的参数类型为包装器类型 TwelveOrLess,相信大家已经猜到了,
init(height: Int = 1, width: TwelveOrLess = TwelveOrLess()) {
_height = TwelveOrLess(wrappedValue: height)
_width = width
}
下面我们把 TwelveOrLess 自定义的 init() 去掉,TwelveOrLess 代码为上面未增加自定义的 init() 之前的代码。width 未指定初始值,包装器有初始化器 init(wrappedValue:),并且没有 init(),因此对应的参数类型为原始类型,因此生成的 memberwise 初始化器为
init(height: Int = 1, width: Int) {
_height = TwelveOrLess(wrappedValue: height)
_width = TwelveOrLess(wrappedValue: width)
}
上面的访问级别都是默认的 internal,下面我们看看属性的访问级别为 private 对于 memberwise 初始化器的生成有什么影响。 我们把属性 width 改为 private,
struct SmallRectangle {
@TwelveOrLess var height: Int = 1
@TwelveOrLess private var width: Int
}
生成的初始化器仍为 init(height: Int = 1, width: Int),但是由于 width 属性是 private,此初始化器的访问级别也是 private,在 SmallRectangle 所在的文件调用它的此初始化器会报错,'SmallRectangle' initializer is inaccessible due to 'private' protection level。
下面我们只把属性 height 改为 private,
struct SmallRectangle {
@TwelveOrLess private var height: Int = 1
@TwelveOrLess var width: Int
}
此时生成的初始化器为
init(width: Int) {
_width = TwelveOrLess(wrappedValue: width)
}
排除了 private 并且指定初始值的 height。
总结:private 并且指定了初始值的属性,生成的初始化器中不包含相应的参数。
除了被包装的值,属性包装器可以通过定义一个 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。