在日常开发中,当我们使用UserDefaults读取或者写入一些值时,通常会写以下的代码:
// 设置
UserDefaults.standard.setValue(newValue, forKey: "xxx")
// 获取
let value = UserDefaults.standard.value(forKey: "xxx")
如果我们希望在获取某个key对应的value失败后能有一个默认值,代码会是如下这样:
let value = UserDefaults.standard.value(forKey: "xxx") ?? defaultValue
在以上这些情况下,Swift 5.1的属性包装器功能非常有用,因为它使我们能够直接将这些行为和逻辑(UserDefaults的set和get方法)附加到属性本身上,这通常为代码重用和泛化开辟了新的可能性。今天,让我们来看看属性包装器的工作原理,并探索一些实际应用中可以使用它们的情况。
import Foundation
@propertyWrapper struct UserDefaultWrapper<T> {
var key: String
var wrappedValue: T? {
get {
return UserDefaults.standard.value(forKey: key) as? T
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
}
}
init(key: String) {
self.key = key
}
}
class MyApp {
@UserDefaultWrapper<String>(key: "userName")
var userName
func loadUserName() -> String? {
return userName
}
func setUserName(name: String) {
userName = name
}
}
let app = MyApp()
app.userName = "123"
let name = app.loadUserName()
print(name)
对于"userName"这个key的沙盒读写操作全部都被封装到了UserDefaultWrapper中,只需要在声明userName属性前使用@UserDefaultWrapper标注,并指定对应的key值。以后每当我们访问该属性时会直接从沙盒读取。同样,赋值操作也会存储到沙盒对应的key中。这样一来,我们的MyApp类中就不需要再出现UserDefaults相关的代码了,相比以前干净了许多。
然而,以上代码的逻辑还不够完善,因为我们还希望当某个key在沙盒中没有value时有一个默认值兜底,所以我们需要改进一下我们的代码。
我们对MyApp的UserName属性做了修改,给它赋上了一个默认值"defaultValue",并且在UserDefaultWrapper中使用了一个属性将defaultValue保存了起来。
@propertyWrapper struct UserDefaultWrapper<T> {
// ...
var defaultValue: T
init(wrappedValue defaultValue: T, key: String) {
self.defaultValue = defaultValue
self.key = key
}
// ...
}
class MyApp {
// ...
@UserDefaultWrapper(key: "undefined_key")
var userName: String = "defaultValue"
// ...
}
此时@UserDefaultWrapper(key: "undefined_key")
其实是在调用UserDefaultWrapper
的init(wrappedValue defaultValue: T, key: String)
方法。只不过编译器帮我们做了优化,会自动把“defaultValue”参数填入wrappedValue
中。所以说,在使用UserDefaultWrapper
时我们可以不用显示地写wrappedValue
参数了。
当我们需要默认值时,我们在声明属性时直接赋上默认值
当我们不需要默认值时,我们将属性直接声明为可选类型
修改后的代码如下:
@propertyWrapper struct UserDefaultWrapper<T> {
var key: String
var defaultValue: T
var wrappedValue: T {
get {
let value = UserDefaults.standard.value(forKey: key) as? T
return value ?? defaultValue
}
set {
UserDefaults.standard.setValue(newValue, forKey: key)
}
}
init(wrappedValue defaultValue: T, key: String) {
self.defaultValue = defaultValue
self.key = key
}
}
class MyApp {
@UserDefaultWrapper(key: "name")
var userName: String = "defaultValue"
@UserDefaultWrapper(wrappedValue: nil, key: "age")
var userAge: Int?
func loadUserName() -> String? {
return userName
}
func setUserName(name: String) {
userName = name
}
}
- 为了防止对一个key设置nil导致崩溃
Attempt to set a non-property-list object <null> as an NSUserDefaults/CFPreferences value for key age
我们需要判断一下传入的value是否为nil - 并且我们希望在以下情况能默认将nil赋值给wrappedValue
@UserDefaultWrapper(wrappedValue: nil, key: "age")
var userAge: Int?
最终代码如下:
import Foundation
extension UserDefaults {
static let testStorage: UserDefaults = UserDefaults.init(suiteName: "test")!
}
@propertyWrapper struct UserDefaultWrapper<T> {
var key: String
var defaultValue: T
var storage: UserDefaults
var wrappedValue: T {
get {
let result = storage.value(forKey: key) as? T
return result ?? defaultValue
}
set {
if let optional = newValue as? AnyOptional {
if let value = optional.getRootWrapped() {
storage.setValue(value, forKey: key)
} else {
storage.removeObject(forKey: key)
}
} else {
storage.setValue(newValue, forKey: key)
}
}
}
init(wrappedValue defaultValue: T,
key: String,
storage: UserDefaults = UserDefaults.standard) {
self.defaultValue = defaultValue
self.key = key
self.storage = storage
}
}
extension UserDefaultWrapper where T: ExpressibleByNilLiteral {
init(key: String,
storage: UserDefaults = UserDefaults.standard) {
self.defaultValue = nil
self.key = key
self.storage = storage
}
}
protocol AnyOptional {
func getRootWrapped() -> Any?
}
extension Optional: AnyOptional {
func getRootWrapped() -> Any? {
switch self {
case .some(let wrapped as AnyOptional):
return wrapped.getRootWrapped()
case .some(let wrapped):
return wrapped
case .none:
return nil
}
}
}