Fix for freeze in `MenuBarExtra` and `NavigationStack` (#158)

This commit is contained in:
Wouter Hennen 2024-01-18 17:10:16 +01:00 committed by GitHub
parent 46dfe48645
commit 2dad0e446e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 24 additions and 14 deletions

View File

@ -6,14 +6,18 @@ extension Defaults {
final class Observable<Value: Serializable>: ObservableObject { final class Observable<Value: Serializable>: ObservableObject {
private var cancellable: AnyCancellable? private var cancellable: AnyCancellable?
private var task: Task<Void, Never>? private var task: Task<Void, Never>?
private let key: Defaults.Key<Value>
let objectWillChange = ObservableObjectPublisher() var key: Defaults.Key<Value> {
didSet {
if key != oldValue {
observe()
}
}
}
var value: Value { var value: Value {
get { Defaults[key] } get { Defaults[key] }
set { set {
objectWillChange.send()
Defaults[key] = newValue Defaults[key] = newValue
} }
} }
@ -21,10 +25,20 @@ extension Defaults {
init(_ key: Key<Value>) { init(_ key: Key<Value>) {
self.key = key self.key = key
observe()
}
deinit {
task?.cancel()
}
func observe() {
// We only use this on the latest OSes (as of adding this) since the backdeploy library has a lot of bugs. // We only use this on the latest OSes (as of adding this) since the backdeploy library has a lot of bugs.
if #available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) { if #available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) {
task?.cancel()
// The `@MainActor` is important as the `.send()` method doesn't inherit the `@MainActor` from the class. // The `@MainActor` is important as the `.send()` method doesn't inherit the `@MainActor` from the class.
self.task = .detached(priority: .userInitiated) { @MainActor [weak self] in task = .detached(priority: .userInitiated) { @MainActor [weak self, key] in
for await _ in Defaults.updates(key) { for await _ in Defaults.updates(key) {
guard let self else { guard let self else {
return return
@ -34,7 +48,7 @@ extension Defaults {
} }
} }
} else { } else {
self.cancellable = Defaults.publisher(key, options: [.prior]) cancellable = Defaults.publisher(key, options: [.prior])
.sink { [weak self] change in .sink { [weak self] change in
guard change.isPrior else { guard change.isPrior else {
return return
@ -47,10 +61,6 @@ extension Defaults {
} }
} }
deinit {
task?.cancel()
}
/** /**
Reset the key back to its default value. Reset the key back to its default value.
*/ */
@ -71,8 +81,7 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
private let key: Defaults.Key<Value> private let key: Defaults.Key<Value>
// Intentionally using `@ObservedObjected` over `@StateObject` so that the key can be dynamically changed. @StateObject private var observable: Defaults.Observable<Value>
@ObservedObject private var observable: Defaults.Observable<Value>
/** /**
Get/set a `Defaults` item and also have the view be updated when the value changes. This is similar to `@State`. Get/set a `Defaults` item and also have the view be updated when the value changes. This is similar to `@State`.
@ -99,7 +108,7 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
*/ */
public init(_ key: Defaults.Key<Value>) { public init(_ key: Defaults.Key<Value>) {
self.key = key self.key = key
self.observable = .init(key) self._observable = .init(wrappedValue: .init(key))
} }
public var wrappedValue: Value { public var wrappedValue: Value {
@ -122,6 +131,7 @@ public struct Default<Value: Defaults.Serializable>: DynamicProperty {
public var publisher: Publisher { Defaults.publisher(key) } public var publisher: Publisher { Defaults.publisher(key) }
public mutating func update() { public mutating func update() {
observable.key = key
_observable.update() _observable.update()
} }