UserDefaults可以存储一些基础数据类型,如:Data,String,Date,Bool,Int,Double,Float,Array,Dictionary,URL,等基础对象。也可以存储自定义对象(但需要实现编码成Data进行存储)。
尽管如此,我们还是不推荐使用UserDefaults来存储数据量大的数据。因为读写非常昂贵,userDefaults最终用.plist文件进行存储。存储大数据会使此文件变得臃肿。
对于自定义的对象,我们可以通过写文件方式将json写到沙盒文件中。或者存储到coreData. Sqlite等。UserDefaults一般用于存储一些简单的数据类型,存储用户的偏好设置等。在写少读多的情况下,性能才会高。底层是通过读写xml文件方式都数据进行读写。

接下来我们用PropertyWrapper对UserDefaults进行封装。

1
2
3
4
5
6
7
8
9
@propertyWrapper
struct UserDefault<T: PropertyListValue> {
let key: Key

var wrappedValue: T? {
get { UserDefaults.standard.value(forKey: key.rawValue) as? T }
set { UserDefaults.standard.set(newValue, forKey: key.rawValue) }
}
}

需要对传入的泛型数据做约束,遵循PropertyListValue协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义约束协议
protocol PropertyListValue {}

extension Data: PropertyListValue {}
extension String: PropertyListValue {}
extension Date: PropertyListValue {}
extension Bool: PropertyListValue {}
extension Int: PropertyListValue {}
extension Double: PropertyListValue {}
extension Float: PropertyListValue {}

// 每个元素必须是 PropertyListValue 类型
extension Array: PropertyListValue where Element: PropertyListValue {}
extension Dictionary: PropertyListValue where Key == String, Value: PropertyListValue {}

然后我们定义Key的结构体,遵循 RawRepresentable 协议。
1
2
3
struct Key: RawRepresentable {
let rawValue: String
}

使用:
1
2
3
4
public struct StorageTest {
@UserDefault(key: Key(rawValue: "isFirstLaunch"))
var isFirstLaunchFlag: Bool?
}

为了达到@UserDefault(key: “isFirstLaunch”)这样的简写效果,我们让Key实现ExpressibleByStringLiteral协议。
1
2
3
4
5
6
7
8
9
10
extension Key: ExpressibleByStringLiteral {
init(stringLiteral: String) {
rawValue = stringLiteral
}
}

public struct StorageTest {
@UserDefault(key: "isFirstLaunch")
var isFirstLaunchFlag: Bool?
}

为了统一的管理,我们将存储键写在Key结构体中。
1
2
3
4
5
6
7
extension Key {
static let isFirstLaunch: Key = "isFirstLaunch"
}
public struct StorageTest {
@UserDefault(key: .isFirstLaunch)
var isFirstLaunchFlag: Bool?
}

KVO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class DefaultsObservation: NSObject {
let key: Key
private var onChange: (Any, Any) -> Void

// 1
init(key: Key, onChange: @escaping (Any, Any) -> Void) {
self.onChange = onChange
self.key = key
super.init()
UserDefaults.standard.addObserver(self, forKeyPath: key.rawValue, options: [.old, .new], context: nil)
}

// 2
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let change = change, object != nil, keyPath == key.rawValue else { return }
onChange(change[.oldKey] as Any, change[.newKey] as Any)
}

// 3
deinit {
UserDefaults.standard.removeObserver(self, forKeyPath: key.rawValue, context: nil)
}
}

@propertyWrapper
struct UserDefault<T: PropertyListValue> {
var projectedValue: UserDefault<T> { return self }

func observe(change: @escaping (T?, T?) -> Void) -> NSObject {
return DefaultsObservation(key: key) { old, new in
change(old as? T, new as? T)
}
}

// The rest of the code is unchanged
}

用法:

1
2
3
4
5
6
7
8
var storage = Storage()

var observation = storage.$isFirstLaunch.observe { old, new in
print("Changed from: \(old) to \(new)")
}

storage.isFirstLaunch = true
storage.isFirstLaunch?.toggle()

评论