iOS Developer

Property Wrapper优化UserDefaults的使

2023-06-29  本文已影响0人  陌浅Ivan

目的

简化保存UserDefaults的写法,通过正常属性赋值取值的方式进行UserDefaults的存取。
比如,正常,保存和读取UserDefaults是这样的:

UserDefaults.standard.setValue("aaa", forKey: "stringvalue")
UserDefaults.standard.value(forKey: "stringvalue") as? String

如果我想要这样做呢?

// 保存到UserDefaults中
UserDefaults.stringValue = "aaa"
// 从UserDefaults中读取
print(UserDefaults.stringValue)

定义要存取的值

属性保存为UserDefaults的类属性,如:

extension UserDefaults {
    static var boolValue: Bool?
    static var stringValue: String?
    static var dictValue: [String: String]?
    static var dateValue: Date?
}

这样的赋值方式只是保存在了内存里面,接下来要使用Property Wrapper在进行存取的时候额外做点事情,把值保存和读取在UserDefaults里面。

定义Property Wrapper

// 最基本的结构
@propertyWrapper
struct UserDefault<Value> {
    var wrappedValue: Value 
}

现在为wrappedValue设置get和set方法,在这里面进行UserDefaults的存取。
因为取值的时候并不能保证一定能取到,所以要设置一个默认值,在无法取到值的时候使用默认值替代。

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    // 默认值
    let defaultValue: Value

    var wrappedValue: Value {
        get {
            // 在无法从UserDefaults里面取到值的时候,就取默认值
            return UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

为特定属性添加存取特性

extension UserDefaults {
    @UserDefault(key: "boolvalue", defaultValue: false)
    static var boolValue: Bool
    @UserDefault(key: "stingvalue", defaultValue: "default")
    static var stringValue: String
    @UserDefault(key: "dictvalue", defaultValue: [String: String]())
    static var dictValue: [String: String]
    @UserDefault(key: "datevalue", defaultValue: Date())
    static var dateValue: Date
}

之所以把可选值的"?"去掉,是因为UserDefaults.standard.object(forKey: key) as? Value当Value为可选值的时候,即使前面的值为nil,也不会走后面的?? defaultValue了。

添加对nil的支持

如果想要在设置一个属性为nil的时候,不需要设置默认值,那就要添加一个新的初始化方法,在Value为可选值的时候,可以使用该初始化方法。
判断Value是否是可选值可以判断该Value是否遵从ExpressibleByNilLiteral协议。

extension UserDefault where Value: ExpressibleByNilLiteral {

    init(key: String) {
        self.init(key: key, defaultValue: nil)
    }
}

当Value值为可选值的时候,就可以不传默认值了。
当传nil的时候,删除UserDefaults中的key,可以在set中判断newValue是否为nil。我们先设置一个protocol:

protocol AnyOptional {
    var isNil: Bool { get }
}

extension Optional: AnyOptional {
    var isNil: Bool { self == nil }
}

此时就可以用AnyOptional来判断是否是nil了:

@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value

    var wrappedValue: Value {
        get {
            return UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            if let optionalValue = newValue as? AnyOptional, optionalValue.isNil {
                UserDefaults.standard.removeObject(forKey: key)
            } else {
                UserDefaults.standard.set(newValue, forKey: key)
            }
        }
    }
}

此时,我们的属性设置可以为可选值:

extension UserDefaults {
    @UserDefault(key: "boolvalue", defaultValue: false)
    static var boolValue: Bool
    @UserDefault(key: "stingvalue", defaultValue: "default")
    static var stringValue: String
    // 不需要传默认值
    @UserDefault(key: "dictvalue")
    static var dictValue: [String: String]?
    @UserDefault(key: "datevalue", defaultValue: Date())
    static var dateValue: Date
}

添加对自定义模型的支持

UserDefaults不支持class、struct等的类型的保存,需要先进行序列化/反序列化才能进行正常读取。所以,针对这样的情况,对这些非基本类型的保存,就使用另外的Property Wrapper进行单独处理。
比如,一个Person类的保存:

extension UserDefaults {
    @UserDefault(key: "personvalue", defaultValue: Person(name: nil, age: nil))
    static var personValue: Person
}

class Person {
    var name: String?
    var age: Int?

    init(name: String?, age: Int?) {
        self.name = name
        self.age = age
    }
}

let person: Person = Person(name: "Ivan", age: 30)

UserDefaults.personValue = person  // Wrong!!!

以上操作会导致报错。所以我们需要进行额外的操作:

@propertyWrapper
struct UserDefaultCodable<Value: Codable> {
    let key: String
    let defaultValue: Value
    var container: UserDefaults = .standard

    var wrappedValue: Value {
        get {
            if let data = container.object(forKey: key) as? Data,
               let user = try? JSONDecoder().decode(Value.self, from: data) {
                return user
            }
            return  defaultValue
        }
        set {
            if let encoded = try? JSONEncoder().encode(newValue) {
                container.set(encoded, forKey: key)
            }
        }
    }
}

使该类支持Codable:

class Person: Codable {
    var name: String?
    var age: Int?

    init(name: String?, age: Int?) {
        self.name = name
        self.age = age
    }
}

这时候,就可以对非基础类型进行正常的存取了。

上一篇下一篇

猜你喜欢

热点阅读