import Foundation /// The default app storage provider for apps which don't specify a /// custom one. /// /// This uses `UserDefaults` on all platforms. public typealias DefaultAppStorageProvider = UserDefaultsAppStorageProvider /// A type that can be used to persist ``AppStorage`` values to disk. public protocol AppStorageProvider: Sendable { /// Persists the given value. /// /// - Parameters: /// - value: The value to persist. /// - key: The key to assign the value to. func persistValue(_ value: Value, forKey key: String) throws /// Retrieves the value for the given key. /// /// - Parameters: /// - type: The type that you expect the value to be. /// - key: The key to retrieve the value from. /// - Returns: The persisted value for `key`, if it exists and is of the /// expected type; otherwise, `nil`. func retrieveValue(ofType type: Value.Type, forKey key: String) -> Value? } /// A simple app storage provider that uses `UserDefaults` to persist /// data. /// /// This works on all supported platforms. public struct UserDefaultsAppStorageProvider: AppStorageProvider { public func persistValue(_ value: Value, forKey key: String) throws { _ = try JSONEncoder().encode(value) // let jsonString = String(decoding: jsonData.bytes, as: UTF8.self) //UserDefaults.standard.set(jsonString, forKey: key) } public func retrieveValue(ofType: Value.Type, forKey key: String) -> Value? { return nil } } extension AppStorageProvider { public func getValue(key: String, defaultValue: T) -> T { // If this is the very first time we're reading from this key, it won't // be in the cache yet. In that case, we return the already-persisted value // if it exists, or the default value otherwise; either way, we add it to the // cache so subsequent accesses of `value` won't have to read from disk again. guard let cachedValue = appStorageCache[key] else { let value = self.retrieveValue(ofType: T.self, forKey: key) ?? defaultValue appStorageCache[key] = value return value } // Make sure that we have the right type. guard let typedValue = cachedValue as? T else { logger.warning( Logger.Message(stringLiteral: "'@AppStorage' property is of the wrong type; using default value"), metadata: [ "key": "\(key)", "providedType": "\(T.self)", "actualType": "\(type(of: cachedValue))", ] ) return defaultValue } return typedValue } public func setValue(key: String, newValue: T) { appStorageCache[key] = newValue do { logger.trace(Logger.Message(stringLiteral: "persisting '\(newValue)' for '\(key)'")) try self.persistValue(newValue, forKey: key) } catch { logger.warning( Logger.Message(stringLiteral: "failed to encode '@AppStorage' data"), metadata: [ "value": "\(newValue)", "error": "\(error.localizedDescription)", ] ) } } }