// // KeychainStorage.swift // KeychainStorage // // Created by Javier Cicchelli on 11/12/2022. // Copyright © 2022 Röck+Cöde. All rights reserved. // import Foundation import KeychainAccess import SwiftUI @propertyWrapper public struct KeychainStorage: DynamicProperty { // MARK: Type aliases public typealias Value = Model // MARK: States @State private var value: Value? // MARK: Properties private let key: String private let keychain: Keychainable private let decoder: JSONDecoder = .init() private let encoder: JSONEncoder = .init() public var wrappedValue: Value? { get { value } nonmutating set { value = newValue do { if let newValue { let encodedValue = try encoder.encode(newValue) try keychain.set(encodedValue, key: key, ignoringAttributeSynchronizable: true) } else { try keychain.remove(key, ignoringAttributeSynchronizable: true) } } catch { assertionFailure("The '\(key)' key should have either be set or removed from the keychain storage.") } } } public var projectedValue: Binding { .init { wrappedValue } set: { wrappedValue = $0 } } // MARK: Initialisers public init( key: String, defaultValue: Value? = nil, keychain: Keychainable = Keychain() ) { self.key = key self.keychain = keychain do { guard let data = try keychain.getData(key, ignoringAttributeSynchronizable: true) else { self._value = .init(initialValue: defaultValue) return } do { let model = try decoder.decode(Value.self, from: data) self._value = .init(initialValue: model) } catch { assertionFailure("The data for the '\(key)' key should have been decoded properly.") } } catch { assertionFailure("The data of the '\(key)' key should have been obtained from the keychain storage.") } } }