@propertyWrapper
属性包装器:在定义存储属性时添加一个分离层,代表该属性被包装起来,且在包装器内部可以做一些事情。把一些通用复用的代码放在了包装器中,比如线程安全检查或者数据存储到数据库中。
个人理解:简单定义的属性只能具备存储功能,供其他方法调用。而被属性包装器修饰的属性不仅具备存储能力,还可以具备计算型属性的能力(setter,与 getter),甚至还可以通过属性包装器存储更多变量或者方法。
官方文档: Property Wrappers Property Wrappers 演化进程
下面我们就通过官网示例了解下 Property Wrappers
基本使用
抽象出特点 定义属性包装器
示例中需要定义一个保证属性不大于12的特点的属性包装器,即代表着该属性包装器修饰的的属性都具备不大于12的特点。
@propertyWrapper // 使用@propertyWrapper标记这是一个属性包装器
struct TwelveOrLess { // 可以定义结构体、类、枚举
private var number: Int // 定义一个私有属性存储外部属性的值
init() { self.number = 0 } // 初始化
var wrappedValue: Int { // 属性包装器必须包含一个非 static 的属性,可以在这里进行值或者方法操作
get { return number } // 具备 get 效果 | 也可以实现 willSet\didSet(但是不能同时)
set { number = min(newValue, 12) } // 具备 set 效果
}
}
wrappedValue
相当于外部的属性值,所以我们可以实现setter``getter
来进行值存储或者其他方法调用。
包装器修饰属性
struct SmallRectangle {
@TwelveOrLess var height: Int // 代表 height 属性无论赋予什么值,也不会大于12
@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"
系统内部会进行如下转化效果,通过这种方式就很好理解了,相当实际存储的时 TwelveOrLess
, height
值只是通过 .wappedValue
来获取。
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue } // 相当于即实现了存储效果,又实现了 set/get 效果
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
而且 _height
和_width
是真实存在的,可以访问的。
Tips: 所以不建议属性用下划线开始命名,以免以后和系统的产生冲突。
实现属性包装器初始化
属性包装器不仅可以存储 修饰属性值,可以存储其他值和实现方法
为了具备能为属性添加初始值我们可以定义包装器的初始化方法
@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
}
使用方式@TwelveOrLess var height: Int
直接给外部属性赋值@TwelveOrLess var height: Int = 10
将报错Argument passed to call that takes no arguments
只含 wrappedValue 参数的初始化
init(wrappedValue: Int) { // init(_ wrappedValue: Int)也可以
maximum = 12
number = min(wrappedValue, maximum)
}
如果只有一个wrappedValue
参数,可以直接使用的时候赋初始值。
@TwelveOrLess var height: Int = 10
含其他参数的初始化
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
如果第一个参数为wrappedValue
,则可以直接使用的时候赋初始值
@SmallNumber(maximum: 80) // 推荐 好理解点
var num: Int = 40
// 等效于
@SmallNumber(wrappedValue: 40, maximum: 80) // 通过初始化方法赋值
var num: Int //= 40 此时不能再赋值
相反的wrappedValue
参数放在后面
init(maximum: Int, wrappedValue: Int) {
...
}
使用 @SmallNumber(maximum: 80) var num: Int = 40
报错Argument 'maximum' must precede argument 'wrappedValue'
只能使用@SmallNumber(maximum: 80, wrappedValue: 40)var num: Int //= 40
projectedValue 使用
实现@propertyWrapper
标记之后,除了必须实现 wrappedValue
属性外,还可以实现projectedValue
属性在外部一$
+属性的方式使用。
@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 // 通过.$someNumber访问 projecedValue
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
问:height
、 _height
和$height
分别代表什么,是不是一个类型。
答: 不是同一个类型
- height:对应 wrappedValue 值
- _height: 对应 TwelveOrLess 类型
- $height:对应 projectedValue, 类型不一定和 height 一致
应用场景
- 本地化
- UserDefault 本地化
使用示例
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
enum GlobalSettings {
@UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false)
static var isFooFeatureEnabled: Bool
@UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false)
static var isBarFeatureEnabled: Bool
}
@Animatable(animated: Animations.TranslationAnimation(), configure: {
if isRepeat {
$0.autoreverses = true
$0.repeatCount = 100
}
})
var translationView: UILabel = UILabel()
另外 SwiftUI 使用了大量属性包装器,比如 @State、@Binding
-
SwiftUI @State文档:@State 修饰的属性的
projectedValue
(即.$xxx
) 的类型为Binding<Value>
-
SwiftUI @Binding文档:可以读写真实源数据的值
参考
如何运用 Swift 的属性包装器实现本地化 – 掘金 - CodingSuccess