Skip to content

Instantly share code, notes, and snippets.

@acosmicflamingo
Created March 4, 2026 17:33
Show Gist options
  • Select an option

  • Save acosmicflamingo/9dc97da1e909779b8f94a6454646bab4 to your computer and use it in GitHub Desktop.

Select an option

Save acosmicflamingo/9dc97da1e909779b8f94a6454646bab4 to your computer and use it in GitHub Desktop.
Power Keychain Persistence Strategy via Point-Free's Sharing library
import Dependencies
import Foundation
import Security
import Sharing
// MARK: - DependencyValues
extension DependencyValues {
public var keychainClient: KeychainClient {
get { self[KeychainClient.self] }
set { self[KeychainClient.self] = newValue }
}
}
extension KeychainClient: DependencyKey {
public static let liveValue = Self.system
public static let previewValue = Self.inMemory
public static let testValue = Self.inMemory
}
// MARK: - KeychainClient
public struct KeychainClient: Sendable {
public var save: @Sendable (Data, String) throws -> Void
public var load: @Sendable (String) throws -> Data?
public var delete: @Sendable (String) throws -> Void
}
// MARK: - Live
extension KeychainClient {
public static var system: Self {
let queue = DispatchQueue(label: "com.keychainClient", qos: .userInitiated)
return Self(
save: { data, key in
try queue.sync { try _writeToKeychain(data: data, key: key) }
},
load: { key in
queue.sync { _readFromKeychain(key: key) }
},
delete: { key in
try queue.sync { try _deleteFromKeychain(key: key) }
}
)
}
}
// MARK: - In-Memory (tests & previews)
extension KeychainClient {
public static var inMemory: Self {
let storage = LockIsolated<[String: Data]>([:])
return Self(
save: { data, key in storage.withValue { $0[key] = data } },
load: { key in storage.value[key] },
delete: { key in storage.withValue { _ = $0.removeValue(forKey: key) } }
)
}
}
// MARK: - Unimplemented
extension KeychainClient {
public static var unimplemented: Self {
Self(
save: { _, _ in XCTFail(#"@Dependency(\.keychainClient.save) is unimplemented"#) },
load: { _ in XCTFail(#"@Dependency(\.keychainClient.load) is unimplemented"#); return nil },
delete: { _ in XCTFail(#"@Dependency(\.keychainClient.delete) is unimplemented"#) }
)
}
}
// MARK: - SharedReaderKey Extension
extension SharedReaderKey {
/// Creates a shared key that reads and writes a `Codable` value to the keychain,
/// backed by `KeychainClient` from swift-dependencies.
///
/// Usage:
/// ```swift
/// extension SharedKey where Self == KeychainStorageKey<SubscriptionState>.Default {
/// static var fooState: Self {
/// Self[.keychainStorage("com.myapp.fooState"), default: .init()]
/// }
/// }
///
/// // In a TCA reducer or SwiftUI view:
/// @Shared(.fooState) var fooState
/// ```
public static func keychainStorage<Value: Codable>(
_ key: String,
decoder: JSONDecoder = JSONDecoder(),
encoder: JSONEncoder = JSONEncoder(),
) -> Self where Self == KeychainStorageKey<Value> {
KeychainStorageKey(key: key, decoder: decoder, encoder: encoder)
}
}
// MARK: - KeychainStorageKey
/// A `SharedKey` that persists `Codable` values via `KeychainClient`.
///
/// Use ``SharedReaderKey/keychainStorage(_:decoder:encoder:)`` to create values of this type.
public final class KeychainStorageKey<Value: Codable & Sendable>: SharedKey {
private let key: String
private let decoder: JSONDecoder
private let encoder: JSONEncoder
public var id: KeychainStorageKeyID {
KeychainStorageKeyID(key: key)
}
fileprivate init(key: String, decoder: JSONDecoder, encoder: JSONEncoder) {
self.key = key
self.decoder = decoder
self.encoder = encoder
}
// MARK: SharedReaderKey
public func load(context _: LoadContext<Value>, continuation: LoadContinuation<Value>) {
do {
@Dependency(\.keychainClient) var keychain
guard let data = try keychain.load(key)
else {
continuation.resumeReturningInitialValue()
return
}
continuation.resume(with: Result { try decoder.decode(Value.self, from: data) })
} catch {
continuation.resume(throwing: error)
}
}
/// Uses Darwin notifications to propagate writes to all other `@Shared` instances
/// holding the same key, within the same process.
public func subscribe(
context _: LoadContext<Value>,
subscriber: SharedSubscriber<Value>,
) -> SharedSubscription {
let center = CFNotificationCenterGetDarwinNotifyCenter()
let name = notificationName as CFString
let handler = KeychainNotificationHandler { [weak self] in
guard let self else { return }
do {
@Dependency(\.keychainClient) var keychain
guard let data = try keychain.load(key)
else {
subscriber.yieldReturningInitialValue()
return
}
subscriber.yield(with: Result { try self.decoder.decode(Value.self, from: data) })
} catch {
subscriber.yield(throwing: error)
}
}
let ptr = UncheckedSendable(wrappedValue: Unmanaged.passRetained(handler).toOpaque())
CFNotificationCenterAddObserver(
center,
ptr.wrappedValue,
{ _, ptr, _, _, _ in
guard let ptr else { return }
Unmanaged<KeychainNotificationHandler>.fromOpaque(ptr)
.takeUnretainedValue()
.callback()
},
name,
nil,
.deliverImmediately,
)
return SharedSubscription {
CFNotificationCenterRemoveObserver(
center,
ptr.wrappedValue,
CFNotificationName(name),
nil,
)
Unmanaged<KeychainNotificationHandler>.fromOpaque(ptr.wrappedValue).release()
}
}
// MARK: SharedKey
public func save(_ value: Value, context _: SaveContext, continuation: SaveContinuation) {
do {
@Dependency(\.keychainClient) var keychain
let data = try encoder.encode(value)
try keychain.save(data, key)
// Notify all other @Shared instances watching this key
CFNotificationCenterPostNotification(
CFNotificationCenterGetDarwinNotifyCenter(),
CFNotificationName(notificationName as CFString),
nil,
nil,
true,
)
continuation.resume()
} catch {
continuation.resume(throwing: error)
}
}
private var notificationName: String {
"com.keychainStorageKey.changed.\(key)"
}
}
// MARK: - Supporting Types
/// Bridges the C-style CFNotification callback to a Swift closure.
/// Must be file-scoped (not nested) because Swift classes cannot be nested
/// inside generic functions.
private final class KeychainNotificationHandler {
let callback: () -> Void
init(_ callback: @escaping () -> Void) { self.callback = callback }
}
public struct KeychainStorageKeyID: Hashable {
fileprivate let key: String
}
extension KeychainStorageKey: CustomStringConvertible {
public var description: String { ".keychainStorage(\(key))" }
}
// MARK: - Keychain Internals
private func _baseQuery(key: String) -> [String: Any] {
[
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
]
}
private func _readFromKeychain(key: String) -> Data? {
var query = _baseQuery(key: key)
query[kSecReturnData as String] = kCFBooleanTrue!
query[kSecMatchLimit as String] = kSecMatchLimitOne
var result: AnyObject?
guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess
else { return nil }
return result as? Data
}
private func _writeToKeychain(data: Data, key: String) throws {
let updateStatus = SecItemUpdate(
_baseQuery(key: key) as CFDictionary,
[kSecValueData as String: data] as CFDictionary,
)
if updateStatus == errSecItemNotFound {
var addQuery = _baseQuery(key: key)
addQuery[kSecValueData as String] = data
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
guard addStatus == errSecSuccess
else {
throw KeychainError.writeFailed(status: addStatus)
}
} else if updateStatus != errSecSuccess {
throw KeychainError.writeFailed(status: updateStatus)
}
}
private func _deleteFromKeychain(key: String) throws {
let status = SecItemDelete(_baseQuery(key: key) as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound
else {
throw KeychainError.deleteFailed(status: status)
}
}
// MARK: - Errors
public enum KeychainError: Error, LocalizedError {
case writeFailed(status: OSStatus)
case deleteFailed(status: OSStatus)
public var errorDescription: String? {
switch self {
case let .writeFailed(status): "Keychain write failed with OSStatus \(status)"
case let .deleteFailed(status): "Keychain delete failed with OSStatus \(status)"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment