Skip to content

Commit 2ac26d8

Browse files
committed
Refactor @UserStorage
1 parent 9476a6d commit 2ac26d8

File tree

1 file changed

+105
-36
lines changed

1 file changed

+105
-36
lines changed

Sources/SwiftUIX/Intramodular/Dynamic Properties/UserStorage.swift

Lines changed: 105 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,26 @@ import Foundation
88
import Swift
99
import SwiftUI
1010

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+
1124
@propertyWrapper
1225
@_documentation(visibility: internal)
1326
public struct UserStorage<Value: Codable>: DynamicProperty {
27+
public typealias Configuration = UserStorageConfiguration
28+
29+
let configuration: UserStorageConfiguration<Value>
30+
1431
@PersistentObject private var valueBox: ValueBox
1532

1633
public var wrappedValue: Value {
@@ -36,23 +53,28 @@ public struct UserStorage<Value: Codable>: DynamicProperty {
3653
}
3754

3855
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
4058
self.valueBox._readInitial()
4159
}
4260

61+
init(configuration: UserStorageConfiguration<Value>) {
62+
self.configuration = configuration
63+
self._valueBox = .init(wrappedValue: .init(configuration: configuration))
64+
}
65+
4366
public init(
4467
wrappedValue: Value,
4568
_ key: String,
4669
store: UserDefaults = .standard,
4770
_isStrict: Bool = false
4871
) {
49-
self._valueBox = .init(
50-
wrappedValue: .init(
72+
self.init(
73+
configuration: .init(
5174
key: key,
5275
defaultValue: wrappedValue,
5376
store: store,
54-
_areValuesEqual: { _, _ in nil },
55-
_isStrict: _isStrict
77+
_areValuesEqual: { _, _ in nil }
5678
)
5779
)
5880
}
@@ -63,17 +85,70 @@ public struct UserStorage<Value: Codable>: DynamicProperty {
6385
store: UserDefaults = .standard,
6486
_isStrict: Bool = false
6587
) where Value: Equatable {
66-
self._valueBox = .init(
67-
wrappedValue: .init(
88+
self.init(
89+
configuration: .init(
6890
key: key,
6991
defaultValue: wrappedValue,
7092
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
73115
)
74116
)
75117
}
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+
}
76126

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+
77152
public init(
78153
_ key: String,
79154
store: UserDefaults = .standard,
@@ -115,11 +190,7 @@ extension UserStorage {
115190
private class ValueBox: ObservableObject {
116191
fileprivate var _SwiftUI_DynamicProperty_update_called: Bool = false
117192

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>
123194

124195
fileprivate var storedValue: Value?
125196

@@ -132,17 +203,23 @@ extension UserStorage {
132203
_readLatest()
133204
} set {
134205
do {
135-
objectWillChange.send()
206+
if configuration.deferUpdates {
207+
Task(priority: .userInitiated) { @MainActor in
208+
objectWillChange.send()
209+
}
210+
} else {
211+
objectWillChange.send()
212+
}
136213

137214
storedValue = newValue
138215

139216
_isEncodingValueToStore = true
140217

141-
try store.encode(newValue, forKey: key)
218+
try configuration.store.encode(newValue, forKey: configuration.key)
142219

143220
_isEncodingValueToStore = false
144221
} catch {
145-
if _isStrict {
222+
if configuration._isStrict {
146223
assertionFailure(String(describing: error))
147224
} else {
148225
print(String(describing: error))
@@ -152,17 +229,9 @@ extension UserStorage {
152229
}
153230

154231
init(
155-
key: String,
156-
defaultValue: Value,
157-
store: UserDefaults,
158-
_areValuesEqual: @escaping (Value, Value) -> Bool?,
159-
_isStrict: Bool
232+
configuration: UserStorageConfiguration<Value>
160233
) {
161-
self.key = key
162-
self.defaultValue = defaultValue
163-
self.store = store
164-
self._areValuesEqual = _areValuesEqual
165-
self._isStrict = _isStrict
234+
self.configuration = configuration
166235
}
167236

168237
fileprivate func _readLatest() -> Value {
@@ -175,18 +244,18 @@ extension UserStorage {
175244
let result: Value?
176245

177246
if _SwiftUI_DynamicProperty_update_called {
178-
result = storedValue ?? defaultValue
247+
result = storedValue ?? configuration.defaultValue
179248
} else {
180249
do {
181-
result = try self.store.decode(Value.self, forKey: key)
250+
result = try configuration.store.decode(Value.self, forKey: configuration.key)
182251
} catch {
183252
debugPrint(error)
184253

185254
result = nil
186255
}
187256
}
188257

189-
return result ?? defaultValue
258+
return result ?? configuration.defaultValue
190259
}
191260

192261
fileprivate func _readInitial() {
@@ -195,19 +264,19 @@ extension UserStorage {
195264
}
196265

197266
do {
198-
storedValue = try store.decode(Value.self, forKey: key) ?? defaultValue
267+
storedValue = try configuration.store.decode(Value.self, forKey: configuration.key) ?? configuration.defaultValue
199268
} catch {
200269
handleError(error)
201270
}
202271

203-
storeSubscription = store
204-
.publisher(for: key, type: Any.self)
272+
storeSubscription = configuration.store
273+
.publisher(for: configuration.key, type: Any.self)
205274
.filter { _ in
206275
!self._isEncodingValueToStore
207276
}
208277
.map { (value: Any) -> Value? in
209278
do {
210-
return try self.store.decode(Value.self, from: value)
279+
return try self.configuration.store.decode(Value.self, from: value)
211280
} catch {
212281
self.handleError(error)
213282

@@ -221,7 +290,7 @@ extension UserStorage {
221290
}
222291

223292
if let oldValue = self.storedValue, let newValue {
224-
guard !(self._areValuesEqual(newValue, oldValue) ?? false) else {
293+
guard !(configuration._areValuesEqual(newValue, oldValue) ?? false) else {
225294
return
226295
}
227296
}
@@ -231,7 +300,7 @@ extension UserStorage {
231300
}
232301

233302
private func handleError(_ error: Error) {
234-
if _isStrict {
303+
if configuration._isStrict {
235304
assertionFailure(String(describing: error))
236305
} else {
237306
print(String(describing: error))

0 commit comments

Comments
 (0)