Skip to content

Commit 1064186

Browse files
committed
Improve performance of Defaults.updates()
1 parent 23e0a44 commit 1064186

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

Sources/Defaults/Defaults.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ extension Defaults {
239239
initial: Bool = true
240240
) -> AsyncStream<Value> { // TODO: Make this `some AsyncSequence<Value>` when Swift 6 is out.
241241
.init { continuation in
242-
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
242+
let observation = UserDefaultsKeyObservation2(object: key.suite, key: key.name) { change in
243243
// TODO: Use the `.deserialize` method directly.
244244
let value = KeyChange(change: change, defaultValue: key.defaultValue).newValue
245245
continuation.yield(value)
@@ -275,7 +275,7 @@ extension Defaults {
275275
) -> AsyncStream<Void> { // TODO: Make this `some AsyncSequence<Value>` when Swift 6 is out.
276276
.init { continuation in
277277
let observations = keys.indexed().map { index, key in
278-
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { _ in
278+
let observation = UserDefaultsKeyObservation2(object: key.suite, key: key.name) { _ in
279279
continuation.yield()
280280
}
281281

Sources/Defaults/Observation.swift

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ extension Defaults {
180180
}
181181

182182
guard
183-
selfObject == object as? NSObject,
183+
selfObject == (object as? NSObject),
184184
let change
185185
else {
186186
return
@@ -191,6 +191,69 @@ extension Defaults {
191191
guard !updatingValuesFlag else {
192192
return
193193
}
194+
195+
callback(BaseChange(change: change))
196+
}
197+
}
198+
199+
// Same as the above, but without the lifetime utilities, which slows down invalidation and we don't need them for `.updates()`.
200+
final class UserDefaultsKeyObservation2: NSObject {
201+
typealias Callback = (BaseChange) -> Void
202+
203+
private weak var object: UserDefaults?
204+
private let key: String
205+
private let callback: Callback
206+
private var isObserving = false
207+
208+
init(object: UserDefaults, key: String, callback: @escaping Callback) {
209+
self.object = object
210+
self.key = key
211+
self.callback = callback
212+
}
213+
214+
deinit {
215+
invalidate()
216+
}
217+
218+
func start(options: ObservationOptions) {
219+
object?.addObserver(self, forKeyPath: key, options: options.toNSKeyValueObservingOptions, context: nil)
220+
isObserving = true
221+
}
222+
223+
func invalidate() {
224+
if isObserving {
225+
object?.removeObserver(self, forKeyPath: key, context: nil)
226+
isObserving = false
227+
}
228+
229+
object = nil
230+
}
231+
232+
// swiftlint:disable:next block_based_kvo
233+
override func observeValue(
234+
forKeyPath keyPath: String?,
235+
of object: Any?,
236+
change: [NSKeyValueChangeKey: Any]?, // swiftlint:disable:this discouraged_optional_collection
237+
context: UnsafeMutableRawPointer?
238+
) {
239+
guard let selfObject = self.object else {
240+
invalidate()
241+
return
242+
}
243+
244+
guard
245+
selfObject == (object as? NSObject),
246+
let change
247+
else {
248+
return
249+
}
250+
251+
let key = preventPropagationThreadDictionaryKey
252+
let updatingValuesFlag = (Thread.current.threadDictionary[key] as? Bool) ?? false
253+
guard !updatingValuesFlag else {
254+
return
255+
}
256+
194257
callback(BaseChange(change: change))
195258
}
196259
}

Sources/Defaults/Utilities.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import Foundation
22
import Combine
3-
#if DEBUG
4-
#if canImport(OSLog)
3+
#if DEBUG && canImport(OSLog)
54
import OSLog
65
#endif
7-
#endif
86

97

108
extension String {

0 commit comments

Comments
 (0)