// // Preference.swift // TuskerPreferences // // Created by Shadowfacts on 4/13/24. // import Foundation import Combine // TODO: once we target iOS 17, use Observable for this @propertyWrapper final class Preference: Codable { @Published private(set) var storedValue: Key.Value? var wrappedValue: Key.Value { get { storedValue ?? Key.defaultValue } set { fatalError("unreachable") } } init() { self.storedValue = nil } init(from decoder: any Decoder) throws { if let keyType = Key.self as? any CustomCodablePreferenceKey.Type { self.storedValue = try keyType.decode(from: decoder) as! Key.Value? } else if let container = try? decoder.singleValueContainer() { self.storedValue = try? container.decode(Key.Value.self) } } func encode(to encoder: any Encoder) throws { if let storedValue { if let keyType = Key.self as? any CustomCodablePreferenceKey.Type { func encode(_: K.Type) throws { try K.encode(value: storedValue as! K.Value, to: encoder) } return try _openExistential(keyType, do: encode) } else { var container = encoder.singleValueContainer() try container.encode(storedValue) } } } static subscript( _enclosingInstance instance: PreferenceStore, wrapped wrappedKeyPath: ReferenceWritableKeyPath, storage storageKeyPath: ReferenceWritableKeyPath ) -> Key.Value { get { get(enclosingInstance: instance, storage: storageKeyPath) } set { set(enclosingInstance: instance, storage: storageKeyPath, newValue: newValue) Key.didSet(in: instance, newValue: newValue) } } // for testing only @inline(__always) static func get( enclosingInstance: Enclosing, storage: KeyPath ) -> Key.Value where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher { let pref = enclosingInstance[keyPath: storage] return pref.storedValue ?? Key.defaultValue } // for testing only @inline(__always) static func set( enclosingInstance: Enclosing, storage: KeyPath, newValue: Key.Value ) where Enclosing: ObservableObject, Enclosing.ObjectWillChangePublisher == ObservableObjectPublisher { enclosingInstance.objectWillChange.send() let pref = enclosingInstance[keyPath: storage] pref.storedValue = newValue } var projectedValue: PreferencePublisher { .init(preference: self) } } public struct PreferencePublisher: Publisher { public typealias Output = Key.Value public typealias Failure = Never let preference: Preference public func receive(subscriber: S) where S : Subscriber, Never == S.Failure, Key.Value == S.Input { preference.$storedValue.map { $0 ?? Key.defaultValue }.receive(subscriber: subscriber) } }