【每天学一点】属性包装器PropertyWrapper(2021.2.3)

683 阅读2分钟

学习素材

@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

其它

  1. 文档中还使用了projectedValue来存储是否变更,这个也应用的挺妙的,后期可以考虑考虑怎么使用这个特性。
  • 使用 model.xxx属性 获取 wrappedValue
  • 使用 model.$xxx属性 获取 projectedValue
  1. 感觉可以使用projectedValue来进行数据的校验,比如邮件的合法性、手机号码、长度要求等。