包装器投影值 projectedValue
projectedValue 为 property wrapper 提供了额外的功能(如:标志某个状态,或者记录 property wrapper 内部的变化等)
示例一:通过projectedValue标记属性是否被修正过
@propertyWrapper
struct TwelveOrLess {
private var number = 0
// 包装器的投影值,标记数值修改状态
var projectedValue:Bool = false
var wrappedValue: Int {
get {return number}
set {
number = min(newValue, 12)
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init(wrappedValue: Int = 0) {
self.number = wrappedValue
}
}
struct SmallRectangle {
@TwelveOrLess var height : Int = 4
@TwelveOrLess var width : Int = 60
}
var rectangle = SmallRectangle(height: 10, width: 25)
rectangle.height = 10
rectangle.width = 20
print("height = \(rectangle.height) - 是否被修改过 = \(rectangle.$height)")
print("width = \(rectangle.width) - 是否被修改过 = \(rectangle.$width)")
运行结果:
height = 10 - 是否被修改过 = false
width = 12 - 是否被修改过 = true
示例二:通过projectedValue直接返回self,为propertyWrapper 提供辅助能力,将RGB色值转化为十六进制
@propertyWrapper
struct RGBValue {
private var value : Int = 0
// 通过 projectedValue 直接返回self,为 propertyWrapper 提供辅助能力
var projectedValue : RGBValue { self }
var wrappedValue: Int {
get { return value }
set { value = max(0, min(255, newValue)) }
}
var hex:String {
String(format: "%02x", value)
}
}
struct RGB {
@RGBValue var r : Int
@RGBValue var g : Int
@RGBValue var b : Int
func hexRGB() -> String {
let rHex = $r.hex // 在属性名前加$访问projectedValue
let gHex = $g.hex
let bHex = $b.hex
return "#\(rHex)\(gHex)\(bHex)"
}
}
var rgb = RGB()
rgb.r = 32
rgb.g = 255
rgb.b = 22
print(rgb.hexRGB())
运行结果:
#20ff16
总结:
- projectedValue 可以是任意类型;
- projectedValue可是存储属性,也可以是计算属性;
- 两者都是通过实例的属性名进行访问的。不同的是 projectedValue 需要在属性名前加上$才可以访问;
- wrappedValue: 实例.属性名,属性包装存储的值;
- projectedValue: 实例.$属性名,映射值;
- projectedValue的命名是固定的。
@propertyWrapper使用示例
一、验证字符串值是否为空
@propertyWrapper
struct CheckEmptyString {
private var value:String = ""
var wrappedValue: String {
get { value }
set {
if newValue.isEmpty {
//assert(false,"传入的值为空")
self.value = "unknowed"
} else {
self.value = newValue
}
}
}
// 初始化方法 参数一定要叫wrappedValue, 不能修改
init(wrappedValue value: String) {
self.value = value
}
}
struct Person {
//2种写法完全等效, 都会进到 init(wrappedValue value: String) 这个方法
@CheckEmptyString(wrappedValue: "张三") var name:String // 设置默认值 "张三"
@CheckEmptyString var job:String = "" // 设置默认值 ""
}
调用:
let zhangsan = Person()
print("name = \(zhangsan.name)")
print("job = \(zhangsan.job)")
结果:
name = 张三
job =
二、给UserDefault提供便利的调用方法
@propertyWrapper
struct MyUserDefault<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 {
@MyUserDefault(key: "AD_ENABLED", defaultValue: false) static var isShowADEnabled:Bool
@MyUserDefault(key: "VersionUpdate_ENABLED", defaultValue: false) static var isShowVersionUpdateEnabled:Bool
}
调用:
GlobalSettings.isShowADEnabled = true
print("获取 GlobalSettings.isShowADEnabled = \(GlobalSettings.isShowADEnabled)")
GlobalSettings.isShowVersionUpdateEnabled = false
print("获取 GlobalSettings.isShowVersionUpdateEnabled = \(GlobalSettings.isShowVersionUpdateEnabled)")
结果
获取 GlobalSettings.isShowADEnabled = true
获取 GlobalSettings.isShowVersionUpdateEnabled = false
三、带参数的属性包装器
@propertyWrapper
struct NumberWrapper {
private var value: Int = 0
private var maxValue: Int = 10
var wrappedValue: Int {
get {
return value
}
set {
// 取 <= maxValue 的值
value = min(newValue, self.maxValue)
}
}
// 参数一定要叫wrappedValue, 不能修改
init(wrappedValue: Int = 0) {
self.value = wrappedValue
}
// 属性包装器的自定义初始化方法
init(wrappedValue: Int, max: Int) {
self.maxValue = max
self.wrappedValue = wrappedValue
}
}
struct MyNumber {
// 告诉编译器使用Wrapper包装器包装该属性
@NumberWrapper var number: Int = 5
// number2和number的效果等价, 注意wrappedValue必须在第一个参数
@NumberWrapper(wrappedValue: 50, max: 100) var number2: Int
@NumberWrapper(max: 20) var number3: Int = 50
}
调用:
let num = MyNumber()
print("number = \(num.number)")
print("number2 = \(num.number2)")
print("number3 = \(num.number3)")
结果:
number = 5
number2 = 50
number3 = 20
这2种写法是完全等价的
- @NumberWrapper(wrappedValue: 50, max: 100) var number2: Int
- @NumberWrapper(max: 20) var number3: Int = 50
四、属性加锁
// 属性加锁使用
@propertyWrapper
class LockAtomic<T> {
private var value: T
private let lock = NSLock()
var wrappedValue: T {
get { getValue() }
set { setValue(newValue: newValue) }
}
init(wrappedValue value: T) {
self.value = value
}
func getValue() -> T {
lock.lock()
defer { lock.unlock() }
return value
}
func setValue(newValue:T) {
lock.lock()
defer { lock.unlock() }
value = newValue
}
}
调用:
@LockAtomic var json : [String:String]?
json = ["a":"1"]
print("json = \(String(describing: json))")
结果:
json = Optional(["a": "1"])