Use the system Keychain in AppKit and UIKit as easily as @AppStorage in SwiftUI — with seamless Codable support and optional value deletion baked in.
- Author: Terence J. Grant
- License: MIT License
- Donate: Your donations are appreciated!
@KeychainStorage is a property wrapper that reflects a system Keychain value, allowing reactive updates via didSet when values change.
This is similar to @DefaultsStorage or @AppStorage for reading values from UserDefaults, but useful for reading and writing secure values to the Keychain.
- Simple property wrapper for storing and retrieving secure values. (Passwords, tokens, session IDs, etc.)
- Supports all standard
Codabletypes, includingString,Int, etc, as well as user-defined,Codableconforming types. - Provides
didSet-based reactivity. - Fully tested for reliability.
Add KeychainStorage to your project with Swift Package Manager in Xcode:
- Go to “File -> Add Package Dependency…”
- Enter the package URL:
https://github.com/tatewake/KeychainStorage - Use “Dependency Rule -> Exact Version” and set to use release version "1.0.0".
The @KeychainStorage tag will load from the Keychain on instantiation and save automatically when the values changes. If no value exists in the Keychain, the initializer will provide a default value.
@KeychainStorage("email") var userEmail = "[email protected]"This syncs automatically on initialization or when modified in code.
Optional values allow you to delete data from the Keychain by setting it to nil:
@KeychainStorage("sessionID") var sessionID: String?
sessionID = "def456" // Store new value into Keychain
sessionID = nil // Delete item from KeychainAny type conforming to Codable works out of the box:
struct User: Codable {
let id: Int
let name: String
}
@KeychainStorage("user") var user: User? = nilThe value is automatically encoded/decoded using JSONEncoder and JSONDecoder.
Here’s an example using UserInfo as a struct to leverage didSet reactivity.
struct UserInfo {
struct Login: Equatable {
@DefaultsStorage("username") var username = ""
@DefaultsStorage("password") var password = ""
}
struct Session: Equatable {
@DefaultsStorage("token") var token: String?
@DefaultsStorage("sessionID") var sessionID: String?
}
var login = Login()
var session = Session()
}Now, in a top-level Model class, we use didSet to handle changes in UserInfo via a delegate:
class Model {
weak var delegate: MyDelegate?
var userInfo = UserInfo() {
didSet {
if oldValue.login != settings.login {
delegate?.LoginChanged(login: settings.login)
}
if oldValue.session != settings.session {
delegate?.SessionChanged(session: settings.session)
}
}
}
...
}This is just a simple example, but it shows the straightforward nature of KeychainStorage.
Much like DefaultsStorage, there are two things to be aware of:
- External changes won’t update in real time, and
- Properties sharing the same key won’t stay in sync.
This is because @KeychainStorage only reads from the Keychain on initialization, and only internal code changes trigger the didSet handler.
Therefore, if another app or process updates your Keychain store, or two @KeychainStorage properties use the same key, these changes won't trigger reactively.
The first scenario is rare, and for the second, ensure a single variable per setting as the “source of truth.”
- Inspired by SwiftUI’s
@AppStorage. - Built as a companion to
DefaultsStoragefor handling secure data.