翻译:PropertyWrapper swift 5 aop特性
说明
swift 5 提出PropertyWrapper,类似于AOP拦截器的思想,在属性的更改之前,做个拦截处理,以下为。
PropertyWrapper
属性包装器在管理属性存储方式的代码与定义属性的代码之间增加了一层隔离。例如,如果您具有提供线程安全检查或将其基础数据存储在数据库中的属性,则必须在每个属性上编写该代码。使用属性包装器时,定义包装器时,只需编写一次管理代码,然后通过将其应用于多个属性来重用该管理代码。
要定义属性包装器,您需要创建定义属性的结构,枚举或类wrappedValue。在下面的代码中,该TwelveOrLess结构确保包装的值始终包含小于或等于12的数字。如果您要求存储更大的数字,则改为存储12。
@propertyWrapper
struct TwelveOrLess {
private var number: Int
init() { self.number = 0 }
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
设置器确保新值小于12,并且getter返回存储的值。
笔记
上例中的声明number将变量标记为private,以确保number仅在的实现中使用TwelveOrLess。在其他地方编写的代码使用的getter和setter访问值wrappedValue,并且不能number直接使用。有关的信息private,请参阅访问控制。
通过在属性之前写包装器的名称作为属性,将包装器应用于属性。这是一个存储一个小矩形的结构,它使用由TwelveOrLess属性包装器实现的“ small”相同(相当随意)的定义:
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
在height和width来自定义性能得到它们的初始值TwelveOrLess,它设置TwelveOrLess.number为零。rectangle.height因为数字很小,所以将数字10存储到成功。尝试存储24实际上存储的是12的值,因为对于属性设置程序的规则而言24太大了。
将包装器应用于属性时,编译器将合成为包装器提供存储的代码和提供通过包装器访问属性的代码。(属性包装器负责存储包装后的值,因此没有为此合成的代码。)您可以编写使用属性包装器的行为的代码,而无需利用特殊的属性语法。例如,这是SmallRectangle先前代码清单的的一个版本,该版本将其属性TwelveOrLess显式地包装在结构中,而不是@TwelveOrLess作为属性来编写:
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包装对wrappedValue属性的访问权的setter和setter 。
设置包装属性的初始值
上面示例中的代码通过number在的定义中提供初始值来设置wrapd属性的初始值TwelveOrLess。使用此属性包装器的代码不能为被包装的属性指定其他初始值,TwelveOrLess例如,SmallRectangle不能给出height或width初始值的定义。为了支持设置初始值或其他自定义,属性包装器需要添加一个初始化程序。下面是一个扩大版TwelveOrLess叫SmallNumber那一套包裹和最大值定义初始化:
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
的定义SmallNumber包括三个初始化器init(),init(wrappedValue:)和init(wrappedValue:maximum:),下面的示例使用这些初始化器来设置包装后的值和最大值。有关初始化和初始化程序语法的信息,请参见Initialization。
当您将包装器应用于属性并且未指定初始值时,Swift使用init()初始化程序来设置包装器。例如:
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
的情况下,SmallNumber该包裹height并width通过调用创建SmallNumber()。该初始化中的代码设置初始包裹值和初始最大值,使用的零和12的默认值的属性包装仍然提供所有的初始值的,如所使用的前面的例子TwelveOrLess在SmallRectangle。与该示例不同,SmallNumber它还支持编写那些初始值作为声明属性的一部分。
当您为属性指定初始值时,Swift使用init(wrappedValue:)初始化程序来设置包装器。例如:
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"
当您使用包装器写属性时,该属性将转换为对初始化器的调用。的情况下,该包裹并通过调用创建。初始化程序使用此处指定的包装值,并且使用默认最大值12。= 1init(wrappedValue:)SmallNumberheightwidthSmallNumber(wrappedValue: 1)
当您在自定义属性后的括号中写入参数时,Swift将使用接受这些参数的初始化程序来设置包装器。例如,如果您提供一个初始值和一个最大值,Swift将使用init(wrappedValue:maximum:)初始化程序:
struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"
的实例SmallNumber,它包装height是通过调用创建,以及包装的实例是通过调用创建。SmallNumber(wrappedValue: 2, maximum: 5)widthSmallNumber(wrappedValue: 3, maximum: 4)
通过包含属性包装器的参数,可以在包装器中设置初始状态,或者在创建包装器时将其他选项传递给包装器。此语法是使用属性包装器的最通用方法。您可以为属性提供所需的任何参数,然后将它们传递给初始化程序。
当包含属性包装器参数时,还可以使用赋值指定初始值。Swift将分配视为一个wrappedValue参数,并使用接受您所包含的参数的初始化程序。例如:
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"
SmallNumber包装的实例height是通过调用来创建的,该实例使用默认的最大值12。包装的实例是通过调用来创建的。SmallNumber(wrappedValue: 1)widthSmallNumber(wrappedValue: 2, maximum: 9)
从属性包装器投影值
除了包装的值之外,属性包装器还可以通过定义投影值来公开其他功能,例如,管理对数据库的访问的属性包装器可以flushDatabaseConnection()在其投影值上公开方法。预计值的名称与包装值相同,但以美元符号( ) 开 头 。 因 为 您 的 代 码 无 法 定 义 以 )开头。因为您的代码无法定义以 )开头。因为您的代码无法定义以投影值开头的属性,所以不会干扰您定义的属性。
在SmallNumber上面的示例中,如果尝试将属性设置为太大的数字,则属性包装器将在存储数字之前对其进行调整。下面的代码projectedValue在SmallNumber结构中添加了一个属性,以在存储该新值之前跟踪该属性包装器是否调整了该属性的新值。
@propertyWrapper
struct SmallNumber {
private var number: Int
var projectedValue: Bool
init() {
self.number = 0
self.projectedValue = false
}
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
写入someStructure. s o m e N u m b e r 访 问 包 装 器 的 预 计 值 。 存 储 样 四 少 数 后 , 的 值 s o m e S t r u c t u r e . someNumber访问包装器的预计值。存储样四少数后,的值someStructure. someNumber访问包装器的预计值。存储样四少数后,的值someStructure.someNumber是false。但是,预计值是true在尝试存储太大的数字(如55 )之后得出的。
属性包装器可以返回任何类型的值作为其投影值。在此示例中,属性包装器仅公开一条信息(无论数字是否已调整),因此它公开该布尔值作为其投影值。需要公开更多信息的包装器可以返回其他某种数据类型的实例,也可以返回self以公开其包装器的实例作为其投影值。
当您从属于类型一部分的代码中访问投影值时(例如,属性获取器或实例方法),可以self.像访问其他属性一样在属性名之前省略。在以下示例中的代码是指围绕包装件的投影值height与width作为 h e i g h t 和 height和 height和width:
enum Size {
case small, large
}
struct SizedRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
mutating func resize(to size: Size) -> Bool {
switch size {
case .small:
height = 10
width = 20
case .large:
height = 100
width = 100
}
return $height || $width
}
}
因为属性包装器语法只是具有getter和setter的属性的语法糖,所以访问height和width行为与访问任何其他属性相同。例如,resize(to:)访问中的代码height及其width使用的属性包装器。如果调用,开关盒将矩形的高度和宽度设置为100。包装器将防止这些属性的值大于12,并将其投影值设置为,以记录其调整其值的事实。最后,return语句检查并确定属性包装器是否已调整或。resize(to: .large).largetrueresize(to:) h e i g h t height heightwidthheightwidth