Contains a single property wrapper, @KeychainStorage, for conviently and securely storing sensitive data in the iOS Keychain.
- Simple, declarative syntax similar to
@AppStorage - Minimalistic implementation—just a single file (should you wish to copy-and-paste!)
- Compatible with Swift 6 and low deployment targets
- Basic Types:
String,Int,Double,Bool - Foundation Types:
URL,Data - Custom Types: Any custom type that that conforms to
Codable
You can integrate KeychainStorageKit into your project using Swift Package Manager:
- In Xcode, select your project in the Project Navigator
- Go to the Package Dependencies tab
- Click the + button to add a package dependency
- In the search bar, enter the repository URL for:
https://github.com/maxhumber/KeychainStorageKit
The syntax of @KeychainStorage is familiar and feels like a close cousin of @AppStorage:
import KeychainStorageKit
func setToken(_ newToken:String){@KeychainStorage("authToken")vartoken:String?
token = newToken
}func getToken()->String?{@KeychainStorage("authToken")vartoken:String?return token
}func removeToken(){@KeychainStorage("authToken")vartoken:String?
token =nil}Here's how you might use the @KeychainStorage wrapper in a SwiftUI app:
import KeychainStorageKit
import SwiftUI
// MARK: - API Client
structFetchClient{varfetch:@Sendable()asyncthrows->Stringstaticletlive=FetchClient(
fetch:{@KeychainStorage("token")vartoken:String?letresult="Result from token: [\(token ??"missing")]" // Use token
return result
})staticletpreview=FetchClient(
fetch:{tryawaitTask.sleep(for:.seconds(2))return"Result for preview [no token]"})}extensionEnvironmentValues{@EntryvarfetchClient:FetchClient=.live
}
// MARK: - View
structContentView:View{@Environment(\.fetchClient)varclient:FetchClient@Statevarresult:String?varbody:someView{VStack{Text(result ??"")Button("Fetch"){Task{awaitfetch()}}}.task{awaittokenRefresh()}}privatefunc fetch()async{
result =try?await client.fetch()}privatefunc tokenRefresh()async{while !Task.isCancelled {letnewToken=String((0..<5).compactMap{ _ in"ABCDEF1234567890".randomElement()})print("New token: [\(newToken)]")@KeychainStorage("token")vartoken:String?
token = newToken // Set token
try?awaitTask.sleep(for:.seconds(5))}}}
// MARK: - Preview
#Preview {ContentView().environment(\.fetchClient,.live) // .environment(\.fetchClient, .preview)
}
// MARK: - Entrypoint
@mainstructKeychainStorageExampleApp:App{varbody:someScene{WindowGroup{ContentView()}}}Due to Keychain access requirements, the tests for this package must run against a "TestHost" app (following this tutorial). To run the tests:
make test