diff --git a/Sources/Sharing/SharedKeys/AppStorageKey.swift b/Sources/Sharing/SharedKeys/AppStorageKey.swift index 1a586ab..530a90d 100644 --- a/Sources/Sharing/SharedKeys/AppStorageKey.swift +++ b/Sources/Sharing/SharedKeys/AppStorageKey.swift @@ -17,8 +17,8 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use + /// the user default store from dependencies. /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -29,8 +29,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -41,8 +40,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -53,8 +51,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -65,8 +62,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -77,8 +73,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -89,8 +84,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -102,10 +96,11 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. - public static func appStorage>(_ key: String, store: UserDefaults? = nil) -> Self + public static func appStorage>( + _ key: String, store: UserDefaults? = nil + ) -> Self where Self == AppStorageKey { AppStorageKey(key, store: store) } @@ -115,10 +110,11 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. - public static func appStorage>(_ key: String, store: UserDefaults? = nil) -> Self + public static func appStorage>( + _ key: String, store: UserDefaults? = nil + ) -> Self where Self == AppStorageKey { AppStorageKey(key, store: store) } @@ -127,8 +123,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -139,8 +134,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -151,8 +145,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -163,8 +156,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -175,8 +167,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -187,8 +178,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -199,8 +189,7 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self where Self == AppStorageKey { @@ -212,10 +201,11 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// the user default store from dependencies. /// - Returns: A user defaults shared key. - public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self + public static func appStorage( + _ key: String, store: UserDefaults? = nil + ) -> Self where Value.RawValue == Int, Self == AppStorageKey { AppStorageKey(key, store: store) } @@ -225,10 +215,11 @@ /// /// - Parameters: /// - key: The key to read and write the value to in the user defaults store. - /// - userDefaults: The user defaults store to read and write to. - /// A value of `nil` will use the user default store from dependencies. + /// - userDefaults: The user defaults store to read and write to. A value of `nil` will use /// - Returns: A user defaults shared key. - public static func appStorage(_ key: String, store: UserDefaults? = nil) -> Self + public static func appStorage( + _ key: String, store: UserDefaults? = nil + ) -> Self where Value.RawValue == String, Self == AppStorageKey { AppStorageKey(key, store: store) } @@ -356,14 +347,16 @@ self.store = UncheckedSendable(store ?? defaultStore) } - fileprivate init>(_ key: String, store: UserDefaults?) where Value == R? { + fileprivate init>(_ key: String, store: UserDefaults?) + where Value == R? { @Dependency(\.defaultAppStorage) var defaultStore self.lookup = OptionalLookup(base: RawRepresentableLookup(base: CastableLookup())) self.key = key self.store = UncheckedSendable(store ?? defaultStore) } - fileprivate init>(_ key: String, store: UserDefaults?) where Value == R? { + fileprivate init>(_ key: String, store: UserDefaults?) + where Value == R? { @Dependency(\.defaultAppStorage) var defaultStore self.lookup = OptionalLookup(base: RawRepresentableLookup(base: CastableLookup())) self.key = key @@ -382,117 +375,111 @@ initialValue: Value?, didSet receiveValue: @escaping @Sendable (_ newValue: Value?) -> Void ) -> SharedSubscription { - #if os(WASI) - return SharedSubscription {} - #else - let previousValue = LockIsolated(initialValue) - let removeObserver: @Sendable () -> Void - let keyContainsPeriod = key.contains(".") - if keyContainsPeriod || key.hasPrefix("@") { - @Dependency(\.appStorageKeyFormatWarningEnabled) var appStorageKeyFormatWarningEnabled - if appStorageKeyFormatWarningEnabled { - let character = keyContainsPeriod ? "." : "@" - reportIssue( - """ - A Shared app storage key (\(key.debugDescription)) contains an invalid character \ - (\(character.debugDescription)) for key-value observation. External updates will be \ - observed less efficiently and accurately via notification center, instead. - - Please reformat this key by removing invalid characters in order to ensure efficient, \ - cross-process observation. - - If you cannot control the format of this key and would like to silence this warning, \ - override the '\\.appStorageKeyFormatWarningEnabled' dependency at the entry point of \ - your application. For example: - - + import Dependencies - -   @main -   struct MyApp: App { -   init() { - + prepareDependencies { - + $0.appStorageKeyFormatWarningEnabled = false - + } -   // ... -   } - -   var body: some Scene { /* ... */ } -   } - """ - ) - } - let userDefaultsDidChange = NotificationCenter.default.addObserver( - forName: UserDefaults.didChangeNotification, - object: store.wrappedValue, - queue: nil - ) { _ in - let newValue = load(initialValue: initialValue) - defer { previousValue.withValue { $0 = newValue } } - func isEqual(_ lhs: T, _ rhs: T) -> Bool? { - func open(_ lhs: U) -> Bool { - lhs == rhs as? U - } - guard let lhs = lhs as? any Equatable else { return nil } - return open(lhs) - } - guard - !(isEqual(newValue, previousValue.value) ?? false) - || (isEqual(newValue, initialValue) ?? true) - else { - return + let previousValue = LockIsolated(initialValue) + let removeObserver: @Sendable () -> Void + let keyContainsPeriod = key.contains(".") + if keyContainsPeriod || key.hasPrefix("@") { + @Dependency(\.appStorageKeyFormatWarningEnabled) var appStorageKeyFormatWarningEnabled + if appStorageKeyFormatWarningEnabled { + let character = keyContainsPeriod ? "." : "@" + reportIssue( + """ + A Shared app storage key (\(key.debugDescription)) contains an invalid character \ + (\(character.debugDescription)) for key-value observation. External updates will be \ + observed less efficiently and accurately via notification center, instead. + + Please reformat this key by removing invalid characters in order to ensure efficient, \ + cross-process observation. + + If you cannot control the format of this key and would like to silence this warning, \ + override the '\\.appStorageKeyFormatWarningEnabled' dependency at the entry point of \ + your application. For example: + + + import Dependencies + +   @main +   struct MyApp: App { +   init() { + + prepareDependencies { + + $0.appStorageKeyFormatWarningEnabled = false + + } +   // ... +   } + +   var body: some Scene { /* ... */ } +   } + """ + ) + } + let userDefaultsDidChange = NotificationCenter.default.addObserver( + forName: UserDefaults.didChangeNotification, + object: store.wrappedValue, + queue: nil + ) { _ in + let newValue = load(initialValue: initialValue) + defer { previousValue.withValue { $0 = newValue } } + func isEqual(_ lhs: T, _ rhs: T) -> Bool? { + func open(_ lhs: U) -> Bool { + lhs == rhs as? U } - guard !SharedAppStorageLocals.isSetting - else { return } - DispatchQueue.main.async { receiveValue(newValue) } + guard let lhs = lhs as? any Equatable else { return nil } + return open(lhs) } - removeObserver = { NotificationCenter.default.removeObserver(userDefaultsDidChange) } - } else { - let observer = Observer { - guard !SharedAppStorageLocals.isSetting - else { return } - receiveValue(load(initialValue: initialValue)) + guard + !(isEqual(newValue, previousValue.value) ?? false) + || (isEqual(newValue, initialValue) ?? true) + else { + return } - store.wrappedValue.addObserver(observer, forKeyPath: key, context: nil) - removeObserver = { store.wrappedValue.removeObserver(observer, forKeyPath: key) } + guard !SharedAppStorageLocals.isSetting + else { return } + DispatchQueue.main.async { receiveValue(newValue) } } - let willEnterForeground: (any NSObjectProtocol)? - if let willEnterForegroundNotificationName { - willEnterForeground = NotificationCenter.default.addObserver( - forName: willEnterForegroundNotificationName, - object: nil, - queue: .main - ) { _ in - receiveValue(load(initialValue: initialValue)) - } - } else { - willEnterForeground = nil + removeObserver = { NotificationCenter.default.removeObserver(userDefaultsDidChange) } + } else { + let observer = Observer { + guard !SharedAppStorageLocals.isSetting + else { return } + receiveValue(load(initialValue: initialValue)) } - return SharedSubscription { - removeObserver() - if let willEnterForeground { - NotificationCenter.default.removeObserver(willEnterForeground) - } + store.wrappedValue.addObserver(observer, forKeyPath: key, context: nil) + removeObserver = { store.wrappedValue.removeObserver(observer, forKeyPath: key) } + } + let willEnterForeground: (any NSObjectProtocol)? + if let willEnterForegroundNotificationName { + willEnterForeground = NotificationCenter.default.addObserver( + forName: willEnterForegroundNotificationName, + object: nil, + queue: .main + ) { _ in + receiveValue(load(initialValue: initialValue)) + } + } else { + willEnterForeground = nil + } + return SharedSubscription { + removeObserver() + if let willEnterForeground { + NotificationCenter.default.removeObserver(willEnterForeground) } - #endif + } } - #if !os(WASI) - private final class Observer: NSObject, Sendable { - let didChange: @Sendable () -> Void - init(didChange: @escaping @Sendable () -> Void) { - self.didChange = didChange - super.init() - } - override func observeValue( - forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey: Any]?, - context: UnsafeMutableRawPointer? - ) { - self.didChange() - } + private final class Observer: NSObject, Sendable { + let didChange: @Sendable () -> Void + init(didChange: @escaping @Sendable () -> Void) { + self.didChange = didChange + super.init() } - #endif + override func observeValue( + forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey: Any]?, + context: UnsafeMutableRawPointer? + ) { + self.didChange() + } + } } extension AppStorageKey: CustomStringConvertible {