学习素材
@propertyWrapper属性包装器可以掉包一个属性,从而达到操作这个属性的目的。理论可以看看学习素材部分,这里讲三个实践
1. Lazy属性
模拟自带的Lazy属性
@propertyWrapper
enum LazyA<Value> {
case uninitialized(() -> Value)
case initialized(Value)
init(wrappedValue: @autoclosure @escaping () -> Value) {
self = .uninitialized(wrappedValue)
}
var wrappedValue: Value {
mutating get {
print("getter")
switch self {
case .uninitialized(let initializer):
let value = initializer()
self = .initialized(value)
return value
case .initialized(let value):
return value
}
}
set {
print("setter")
self = .initialized(newValue)
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
@LazyA var item:String = "Hello"
func applicationDidFinishLaunching(_ aNotification: Notification) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// 此时未初始self.item
print("3s later")
print(self.item) // Hello
self.item = "world"
print(self.item) // world
}
}
}
2. UserDefault数据获取
免去一堆的getter和setter来从UserDefault中获取数据和存储数据
@propertyWrapper
class UserDefaultWrapper<Value> {
private var key: String
private var defaultValue: Value?
init(key: String, defaultValue: Value? = nil) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: Value? {
get {
let std = UserDefaults.standard
return std.object(forKey: key) as? Value ?? defaultValue
}
set {
let std = UserDefaults.standard
if (newValue == nil) {
std.removeObject(forKey: key)
} else {
std.setValue(newValue, forKey: key)
}
std.synchronize()
}
}
}
class USDemoClass {
@UserDefaultWrapper(key: "money", defaultValue: 1) var money: Int?
@UserDefaultWrapper(key: "age") var age: Int?
}
let value1 = USDemoClass()
print("value1.money: \(value1.money ?? 0)") // value1.money: 1
print("value1.age: \(String(describing: value1.age))") // value1.age: nil
// 该值将会写入UserDefault中,下次可以直接从UserDefault中获取
value1.money = 2
value1.age = 18
print("value1.money: \(value1.money ?? 0)") // value1.money: 2
print("value1.money: \(value1.age ?? 0)") // value1.age: 18
3. Range控制
设定最大值和最小值,当设置的值超出范围时自动使用最大值或者最小值。
@propertyWrapper
class RangeWrapper<Value: Comparable> {
private var _min: Value?
private var _max: Value?
private var _value: Value?
private var _wrappedValue: Value
init(wrappedValue: Value, min: Value? = nil, max: Value? = nil) {
self._min = min
self._max = max
self._wrappedValue = wrappedValue
update(value: wrappedValue)
}
var wrappedValue: Value {
get {
return self._value ?? _wrappedValue
}
set {
update(value: newValue)
}
}
func update(value newValue: Value) {
if let min = _min, newValue < min {
self._value = min
} else if let max = _max, newValue > max {
self._value = max
} else {
self._value = newValue
}
}
}
class RangeClass {
@RangeWrapper(min: 1, max: 100) var age: Int = 3
@RangeWrapper(min: 1) var money: Int = 3
@RangeWrapper(max: 60) var minute: Int = 0
}
let value2 = RangeClass();
print(value2.age); // 3
print(value2.money); // 3
print(value2.minute); // 0
value2.age = -1;
print(value2.age); // 1
value2.age = 101;
print(value2.age); // 100
value2.age = 30;
print(value2.age); // 30
value2.money = -1;
print(value2.money); // 1
value2.money = 101;
print(value2.money); // 101
value2.money = 30;
print(value2.money); // 30
value2.minute = -1;
print(value2.minute); // -1
value2.minute = 101;
print(value2.minute); // 60
value2.minute = 30;
print(value2.minute); // 30
其它
- 文档中还使用了
projectedValue来存储是否变更,这个也应用的挺妙的,后期可以考虑考虑怎么使用这个特性。
- 使用 model.xxx属性 获取 wrappedValue
- 使用 model.$xxx属性 获取 projectedValue
- 感觉可以使用projectedValue来进行数据的校验,比如邮件的合法性、手机号码、长度要求等。