From a6dfb7144692c665281daee634a3cb1e48ad2f96 Mon Sep 17 00:00:00 2001 From: Mykyta Konopelko Date: Tue, 15 Oct 2024 01:33:31 +0200 Subject: [PATCH] sometimes KVO is not working, thats why added Notification observer --- Source/Defaults.swift | 158 +++++++++++++++++++++++--------------- Tests/DefaultsTests.swift | 5 +- 2 files changed, 101 insertions(+), 62 deletions(-) diff --git a/Source/Defaults.swift b/Source/Defaults.swift index 5dd528c..9a79539 100644 --- a/Source/Defaults.swift +++ b/Source/Defaults.swift @@ -1,22 +1,29 @@ import Combine import Foundation +@MainActor @propertyWrapper -public final class Defaults { +public final class Defaults { private let userDefaults: UserDefaults private let key: String private let defaultValue: Value private let defaultsObserver: DefaultsObserver + #if swift(>=6.0) + private nonisolated(unsafe) var notificationToken: (any NSObjectProtocol)? + #else + private var notificationToken: (any NSObjectProtocol)? + #endif + private let decoderGenerator: () -> JSONDecoder private lazy var decoder: JSONDecoder = decoderGenerator() private let encoderGenerator: () -> JSONEncoder private lazy var encoder: JSONEncoder = encoderGenerator() private lazy var eventier: CurrentValueSubject = .init(wrappedValue) - public lazy var projectedValue: AnyPublisher = { - return eventier.eraseToAnyPublisher() + public private(set) lazy var projectedValue: AnyPublisher = { + return eventier.removeDuplicates().eraseToAnyPublisher() }() public var wrappedValue: Value { @@ -50,8 +57,8 @@ public final class Defaults { } } - public required init(_ key: String, - defaultValue: Value, + public required init(wrappedValue defaultValue: Value, + key: String, decoder: (() -> JSONDecoder)? = nil, encoder: (() -> JSONEncoder)? = nil, userDefaults: UserDefaults = .standard) { @@ -62,67 +69,59 @@ public final class Defaults { self.decoderGenerator = decoder ?? { .init() } self.defaultsObserver = .init(key: key, userDefaults: userDefaults) - defaultsObserver.updateHandler = { [weak self] new in - guard let self else { - return - } - - let newRestored: Value - if let new = new as? Value { - newRestored = new - } else if new is NSNull { - newRestored = self.defaultValue - } else if let new = new as? Data { - do { - newRestored = try self.decoder.decode(Value.self, from: new) - } catch { - newRestored = self.defaultValue - } - } else { - assertionFailure("somehow value in defaults was overridden with wrong type") - newRestored = self.defaultValue - } + defaultsObserver.updateHandler = { [weak self] _ in + self?.syncMain() + } - eventier.send(newRestored) + // sometimes KVO is not working + self.notificationToken = NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, + object: userDefaults, + queue: .main) { [weak self] _ in + self?.syncMain() } } -} - -private final class DefaultsObserver: NSObject { - private let userDefaults: UserDefaults - private let key: String - #if swift(>=6.0) - var updateHandler: (@Sendable (_ new: Any?) -> Void)? - #else - var updateHandler: ((_ new: Any?) -> Void)? - #endif - - required init(key: String, - userDefaults: UserDefaults) { - self.key = key - self.userDefaults = userDefaults - super.init() - userDefaults.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil) + deinit { + if let notificationToken { + NotificationCenter.default.removeObserver(notificationToken) + } } - deinit { - userDefaults.removeObserver(self, forKeyPath: key, context: nil) + private nonisolated func syncMain() { + assert(Thread.isMainThread, "Should be used only in main thread") + + #if swift(>=6.0) + MainActor.assumeIsolated { + notifyAboutChanges() + } + #else + notifyAboutChanges() + #endif } - override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - guard let change, keyPath == key else { + private func notifyAboutChanges() { + guard let new = userDefaults.object(forKey: key) else { + eventier.send(defaultValue) return } - let new = UnsafeSendable(change[.newKey]) - if Thread.isMainThread { - updateHandler?(new.value) - } else { - DispatchQueue.main.sync { - updateHandler?(new.value) + let newRestored: Value + if let new = new as? Value { + newRestored = new + } else if new is NSNull { + newRestored = defaultValue + } else if let new = new as? Data { + do { + newRestored = try decoder.decode(Value.self, from: new) + } catch { + newRestored = defaultValue } + } else { + assertionFailure("somehow value in defaults was overridden with wrong type") + newRestored = defaultValue } + + eventier.send(newRestored) } } @@ -131,8 +130,8 @@ public extension Defaults where Value: ExpressibleByNilLiteral { decoder: (() -> JSONDecoder)? = nil, encoder: (() -> JSONEncoder)? = nil, userDefaults: UserDefaults = .standard) { - self.init(key, - defaultValue: nil, + self.init(wrappedValue: nil, + key: key, decoder: decoder, encoder: encoder, userDefaults: userDefaults) @@ -144,8 +143,8 @@ public extension Defaults where Value: ExpressibleByArrayLiteral, Value.ArrayLit decoder: (() -> JSONDecoder)? = nil, encoder: (() -> JSONEncoder)? = nil, userDefaults: UserDefaults = .standard) { - self.init(key, - defaultValue: [], + self.init(wrappedValue: [], + key: key, decoder: decoder, encoder: encoder, userDefaults: userDefaults) @@ -157,8 +156,8 @@ public extension Defaults where Value: ExpressibleByDictionaryLiteral, Value.Key decoder: (() -> JSONDecoder)? = nil, encoder: (() -> JSONEncoder)? = nil, userDefaults: UserDefaults = .standard) { - self.init(key, - defaultValue: [:], + self.init(wrappedValue: [:], + key: key, decoder: decoder, encoder: encoder, userDefaults: userDefaults) @@ -167,10 +166,49 @@ public extension Defaults where Value: ExpressibleByDictionaryLiteral, Value.Key #if swift(>=6.0) extension Defaults: @unchecked Sendable {} -extension DefaultsObserver: @unchecked Sendable {} #endif +private final class DefaultsObserver: NSObject { + private let userDefaults: UserDefaults + private let key: String + + #if swift(>=6.0) + var updateHandler: (@Sendable (_ new: Any?) -> Void)? + #else + var updateHandler: ((_ new: Any?) -> Void)? + #endif + + required init(key: String, + userDefaults: UserDefaults) { + self.key = key + self.userDefaults = userDefaults + super.init() + userDefaults.addObserver(self, forKeyPath: key, options: [.old, .new], context: nil) + } + + deinit { + userDefaults.removeObserver(self, forKeyPath: key, context: nil) + } + + override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + guard let change, keyPath == key else { + return + } + + let new = UnsafeSendable(change[.newKey]) + if Thread.isMainThread { + updateHandler?(new.value) + } else { + DispatchQueue.main.sync { + updateHandler?(new.value) + } + } + } +} + #if swift(>=6.0) +extension DefaultsObserver: @unchecked Sendable {} + private struct UnsafeSendable: @unchecked Sendable { let value: T } diff --git a/Tests/DefaultsTests.swift b/Tests/DefaultsTests.swift index d157536..903a300 100644 --- a/Tests/DefaultsTests.swift +++ b/Tests/DefaultsTests.swift @@ -3,6 +3,7 @@ import Foundation import StorageKit import XCTest +@MainActor final class DefaultsTests: XCTestCase { fileprivate struct Custom: Codable, Equatable { let value: Int @@ -105,8 +106,8 @@ extension DefaultsTests { let encoder = JSONEncoder() var observers: Set = [] - @Defaults(key, defaultValue: defaultValue, userDefaults: userDefaults) - var varValue: T + @Defaults(key: key, userDefaults: userDefaults) + var varValue: T = defaultValue $varValue.sink { new in results.append(new)