Improve performance of `Defaults.updates()`

This commit is contained in:
Sindre Sorhus 2024-04-24 02:22:25 +07:00
parent 23e0a4497e
commit 1064186b3d
3 changed files with 67 additions and 6 deletions

View File

@ -239,7 +239,7 @@ extension Defaults {
initial: Bool = true
) -> AsyncStream<Value> { // TODO: Make this `some AsyncSequence<Value>` when Swift 6 is out.
.init { continuation in
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
let observation = UserDefaultsKeyObservation2(object: key.suite, key: key.name) { change in
// TODO: Use the `.deserialize` method directly.
let value = KeyChange(change: change, defaultValue: key.defaultValue).newValue
continuation.yield(value)
@ -275,7 +275,7 @@ extension Defaults {
) -> AsyncStream<Void> { // TODO: Make this `some AsyncSequence<Value>` when Swift 6 is out.
.init { continuation in
let observations = keys.indexed().map { index, key in
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { _ in
let observation = UserDefaultsKeyObservation2(object: key.suite, key: key.name) { _ in
continuation.yield()
}

View File

@ -180,7 +180,7 @@ extension Defaults {
}
guard
selfObject == object as? NSObject,
selfObject == (object as? NSObject),
let change
else {
return
@ -191,6 +191,69 @@ extension Defaults {
guard !updatingValuesFlag else {
return
}
callback(BaseChange(change: change))
}
}
// Same as the above, but without the lifetime utilities, which slows down invalidation and we don't need them for `.updates()`.
final class UserDefaultsKeyObservation2: NSObject {
typealias Callback = (BaseChange) -> Void
private weak var object: UserDefaults?
private let key: String
private let callback: Callback
private var isObserving = false
init(object: UserDefaults, key: String, callback: @escaping Callback) {
self.object = object
self.key = key
self.callback = callback
}
deinit {
invalidate()
}
func start(options: ObservationOptions) {
object?.addObserver(self, forKeyPath: key, options: options.toNSKeyValueObservingOptions, context: nil)
isObserving = true
}
func invalidate() {
if isObserving {
object?.removeObserver(self, forKeyPath: key, context: nil)
isObserving = false
}
object = nil
}
// swiftlint:disable:next block_based_kvo
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?, // swiftlint:disable:this discouraged_optional_collection
context: UnsafeMutableRawPointer?
) {
guard let selfObject = self.object else {
invalidate()
return
}
guard
selfObject == (object as? NSObject),
let change
else {
return
}
let key = preventPropagationThreadDictionaryKey
let updatingValuesFlag = (Thread.current.threadDictionary[key] as? Bool) ?? false
guard !updatingValuesFlag else {
return
}
callback(BaseChange(change: change))
}
}

View File

@ -1,10 +1,8 @@
import Foundation
import Combine
#if DEBUG
#if canImport(OSLog)
#if DEBUG && canImport(OSLog)
import OSLog
#endif
#endif
extension String {