@@ -8,9 +8,26 @@ import Foundation
8
8
import Swift
9
9
import SwiftUI
10
10
11
+ public struct UserStorageConfiguration < Value> {
12
+ let key : String
13
+ let defaultValue : Value
14
+ let store : UserDefaults
15
+ var _areValuesEqual : ( Value , Value ) -> Bool ?
16
+ var _isStrict : Bool = false
17
+ var deferUpdates : Bool = false
18
+ }
19
+
20
+ extension UserStorageConfiguration : @unchecked Sendable where Value: Sendable {
21
+
22
+ }
23
+
11
24
@propertyWrapper
12
25
@_documentation ( visibility: internal)
13
26
public struct UserStorage < Value: Codable > : DynamicProperty {
27
+ public typealias Configuration = UserStorageConfiguration
28
+
29
+ let configuration : UserStorageConfiguration < Value >
30
+
14
31
@PersistentObject private var valueBox : ValueBox
15
32
16
33
public var wrappedValue : Value {
@@ -36,23 +53,28 @@ public struct UserStorage<Value: Codable>: DynamicProperty {
36
53
}
37
54
38
55
public mutating func update( ) {
39
- self . valueBox. _SwiftUI_DynamicProperty_update_called = true
56
+ self . valueBox. _SwiftUI_DynamicProperty_update_called = true
57
+ self . valueBox. configuration = configuration
40
58
self . valueBox. _readInitial ( )
41
59
}
42
60
61
+ init ( configuration: UserStorageConfiguration < Value > ) {
62
+ self . configuration = configuration
63
+ self . _valueBox = . init( wrappedValue: . init( configuration: configuration) )
64
+ }
65
+
43
66
public init (
44
67
wrappedValue: Value ,
45
68
_ key: String ,
46
69
store: UserDefaults = . standard,
47
70
_isStrict: Bool = false
48
71
) {
49
- self . _valueBox = . init(
50
- wrappedValue : . init(
72
+ self . init (
73
+ configuration : . init(
51
74
key: key,
52
75
defaultValue: wrappedValue,
53
76
store: store,
54
- _areValuesEqual: { _, _ in nil } ,
55
- _isStrict: _isStrict
77
+ _areValuesEqual: { _, _ in nil }
56
78
)
57
79
)
58
80
}
@@ -63,17 +85,70 @@ public struct UserStorage<Value: Codable>: DynamicProperty {
63
85
store: UserDefaults = . standard,
64
86
_isStrict: Bool = false
65
87
) where Value: Equatable {
66
- self . _valueBox = . init(
67
- wrappedValue : . init(
88
+ self . init (
89
+ configuration : . init(
68
90
key: key,
69
91
defaultValue: wrappedValue,
70
92
store: store,
71
- _areValuesEqual: { $0 == $1 } ,
72
- _isStrict: _isStrict
93
+ _areValuesEqual: { $0 == $1 }
94
+ )
95
+ )
96
+ }
97
+ }
98
+
99
+ // MARK: - Initializers
100
+
101
+ extension UserStorage {
102
+ public init (
103
+ wrappedValue: Value ,
104
+ _ key: String ,
105
+ store: UserDefaults = . standard,
106
+ deferUpdates: Bool
107
+ ) {
108
+ self . init (
109
+ configuration: . init(
110
+ key: key,
111
+ defaultValue: wrappedValue,
112
+ store: store,
113
+ _areValuesEqual: { _, _ in nil } ,
114
+ deferUpdates: deferUpdates
73
115
)
74
116
)
75
117
}
118
+
119
+ public init (
120
+ _ key: String ,
121
+ store: UserDefaults = . standard,
122
+ deferUpdates: Bool
123
+ ) where Value: ExpressibleByNilLiteral {
124
+ self . init ( wrappedValue: nil , key, store: store, deferUpdates: deferUpdates)
125
+ }
76
126
127
+ public init (
128
+ wrappedValue: Value ,
129
+ _ key: String ,
130
+ store: UserDefaults = . standard,
131
+ deferUpdates: Bool
132
+ ) where Value: Equatable & ExpressibleByNilLiteral {
133
+ self . init (
134
+ configuration: . init(
135
+ key: key,
136
+ defaultValue: wrappedValue,
137
+ store: store,
138
+ _areValuesEqual: { $0 == $1 } ,
139
+ deferUpdates: deferUpdates
140
+ )
141
+ )
142
+ }
143
+
144
+ public init (
145
+ _ key: String ,
146
+ store: UserDefaults = . standard,
147
+ deferUpdates: Bool
148
+ ) where Value: Equatable & ExpressibleByNilLiteral {
149
+ self . init ( wrappedValue: nil , key, store: store, deferUpdates: deferUpdates)
150
+ }
151
+
77
152
public init (
78
153
_ key: String ,
79
154
store: UserDefaults = . standard,
@@ -115,11 +190,7 @@ extension UserStorage {
115
190
private class ValueBox : ObservableObject {
116
191
fileprivate var _SwiftUI_DynamicProperty_update_called : Bool = false
117
192
118
- private let key : String
119
- private let defaultValue : Value
120
- private let store : UserDefaults
121
- private let _areValuesEqual : ( Value , Value ) -> Bool ?
122
- private let _isStrict : Bool
193
+ var configuration : UserStorageConfiguration < Value >
123
194
124
195
fileprivate var storedValue : Value ?
125
196
@@ -132,17 +203,23 @@ extension UserStorage {
132
203
_readLatest ( )
133
204
} set {
134
205
do {
135
- objectWillChange. send ( )
206
+ if configuration. deferUpdates {
207
+ Task ( priority: . userInitiated) { @MainActor in
208
+ objectWillChange. send ( )
209
+ }
210
+ } else {
211
+ objectWillChange. send ( )
212
+ }
136
213
137
214
storedValue = newValue
138
215
139
216
_isEncodingValueToStore = true
140
217
141
- try store. encode ( newValue, forKey: key)
218
+ try configuration . store. encode ( newValue, forKey: configuration . key)
142
219
143
220
_isEncodingValueToStore = false
144
221
} catch {
145
- if _isStrict {
222
+ if configuration . _isStrict {
146
223
assertionFailure ( String ( describing: error) )
147
224
} else {
148
225
print ( String ( describing: error) )
@@ -152,17 +229,9 @@ extension UserStorage {
152
229
}
153
230
154
231
init (
155
- key: String ,
156
- defaultValue: Value ,
157
- store: UserDefaults ,
158
- _areValuesEqual: @escaping ( Value , Value ) -> Bool ? ,
159
- _isStrict: Bool
232
+ configuration: UserStorageConfiguration < Value >
160
233
) {
161
- self . key = key
162
- self . defaultValue = defaultValue
163
- self . store = store
164
- self . _areValuesEqual = _areValuesEqual
165
- self . _isStrict = _isStrict
234
+ self . configuration = configuration
166
235
}
167
236
168
237
fileprivate func _readLatest( ) -> Value {
@@ -175,18 +244,18 @@ extension UserStorage {
175
244
let result : Value ?
176
245
177
246
if _SwiftUI_DynamicProperty_update_called {
178
- result = storedValue ?? defaultValue
247
+ result = storedValue ?? configuration . defaultValue
179
248
} else {
180
249
do {
181
- result = try self . store. decode ( Value . self, forKey: key)
250
+ result = try configuration . store. decode ( Value . self, forKey: configuration . key)
182
251
} catch {
183
252
debugPrint ( error)
184
253
185
254
result = nil
186
255
}
187
256
}
188
257
189
- return result ?? defaultValue
258
+ return result ?? configuration . defaultValue
190
259
}
191
260
192
261
fileprivate func _readInitial( ) {
@@ -195,19 +264,19 @@ extension UserStorage {
195
264
}
196
265
197
266
do {
198
- storedValue = try store. decode ( Value . self, forKey: key) ?? defaultValue
267
+ storedValue = try configuration . store. decode ( Value . self, forKey: configuration . key) ?? configuration . defaultValue
199
268
} catch {
200
269
handleError ( error)
201
270
}
202
271
203
- storeSubscription = store
204
- . publisher ( for: key, type: Any . self)
272
+ storeSubscription = configuration . store
273
+ . publisher ( for: configuration . key, type: Any . self)
205
274
. filter { _ in
206
275
!self . _isEncodingValueToStore
207
276
}
208
277
. map { ( value: Any ) -> Value ? in
209
278
do {
210
- return try self . store. decode ( Value . self, from: value)
279
+ return try self . configuration . store. decode ( Value . self, from: value)
211
280
} catch {
212
281
self . handleError ( error)
213
282
@@ -221,7 +290,7 @@ extension UserStorage {
221
290
}
222
291
223
292
if let oldValue = self . storedValue, let newValue {
224
- guard !( self . _areValuesEqual ( newValue, oldValue) ?? false ) else {
293
+ guard !( configuration . _areValuesEqual ( newValue, oldValue) ?? false ) else {
225
294
return
226
295
}
227
296
}
@@ -231,7 +300,7 @@ extension UserStorage {
231
300
}
232
301
233
302
private func handleError( _ error: Error ) {
234
- if _isStrict {
303
+ if configuration . _isStrict {
235
304
assertionFailure ( String ( describing: error) )
236
305
} else {
237
306
print ( String ( describing: error) )
0 commit comments