{"id":22362668,"url":"https://github.com/maxhumber/keychainstoragekit","last_synced_at":"2025-08-16T01:15:49.624Z","repository":{"id":264907966,"uuid":"888073988","full_name":"maxhumber/KeychainStorageKit","owner":"maxhumber","description":"@KeychainStorage property wrapper","archived":false,"fork":false,"pushed_at":"2024-11-14T14:07:12.000Z","size":35,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-06T21:42:36.009Z","etag":null,"topics":["keychain","swift","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxhumber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-13T19:12:01.000Z","updated_at":"2025-03-21T22:02:54.000Z","dependencies_parsed_at":"2024-11-26T21:18:20.517Z","dependency_job_id":null,"html_url":"https://github.com/maxhumber/KeychainStorageKit","commit_stats":null,"previous_names":["maxhumber/keychainstoragekit"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/maxhumber/KeychainStorageKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FKeychainStorageKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FKeychainStorageKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FKeychainStorageKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FKeychainStorageKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxhumber","download_url":"https://codeload.github.com/maxhumber/KeychainStorageKit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FKeychainStorageKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270653590,"owners_count":24622793,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-15T02:00:12.559Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["keychain","swift","swiftui"],"created_at":"2024-12-04T17:10:11.636Z","updated_at":"2025-08-16T01:15:49.597Z","avatar_url":"https://github.com/maxhumber.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"Images/logo.png\" height=\"150\"\u003e\n\n### KeychainStorageKit\n\nContains a single property wrapper, **`@KeychainStorage`**, for conviently and securely storing sensitive data in the iOS Keychain. \n\n#### Features\n\n- Simple, declarative syntax similar to `@AppStorage`\n- Minimalistic implementation—just a [single file](KeychainStorageKit/KeychainStorage.swift) (should you wish to copy-and-paste!)\n- Compatible with Swift 6 and low deployment targets\n\n#### Supported Types\n\n- Basic Types: `String`, `Int`, `Double`, `Bool`\n- Foundation Types: `URL`, `Data`\n- Custom Types: Any custom type that that conforms to `Codable`\n\n#### Installation\n\nYou can integrate `KeychainStorageKit` into your project using Swift Package Manager:\n\n1. In Xcode, select your project in the Project Navigator\n2. Go to the Package Dependencies tab\n3. Click the + button to add a package dependency\n4. In the search bar, enter the repository URL for: `https://github.com/maxhumber/KeychainStorageKit` \n\n#### Usage\n\nThe syntax of **`@KeychainStorage`** is familiar and feels like a close cousin of `@AppStorage`:\n\n```swift\nimport KeychainStorageKit\n\nfunc setToken(_ newToken: String) {\n    @KeychainStorage(\"authToken\") var token: String?\n    token = newToken\n}\n\nfunc getToken() -\u003e String? {\n    @KeychainStorage(\"authToken\") var token: String?\n    return token\n}\n\nfunc removeToken() {\n    @KeychainStorage(\"authToken\") var token: String?\n    token = nil\n}\n```\n\n#### Usage within a SwiftUI App\n\nHere's how you might use the **`@KeychainStorage`** wrapper in a SwiftUI app:\n\n```swift\nimport KeychainStorageKit\nimport SwiftUI\n\n// MARK: - API Client\nstruct FetchClient {\n    var fetch: @Sendable () async throws -\u003e String\n    \n    static let live = FetchClient(\n        fetch: {\n            @KeychainStorage(\"token\") var token: String?\n            let result = \"Result from token: [\\(token ?? \"missing\")]\" // Use token\n            return result\n        }\n    )\n    \n    static let preview = FetchClient(\n        fetch: {\n            try await Task.sleep(for: .seconds(2))\n            return \"Result for preview [no token]\"\n        }\n    )\n}\n\nextension EnvironmentValues {\n    @Entry var fetchClient: FetchClient = .live\n}\n\n// MARK: - View\nstruct ContentView: View {\n    @Environment(\\.fetchClient) var client: FetchClient\n    @State var result: String?\n    \n    var body: some View {\n        VStack {\n            Text(result ?? \"\")\n            Button(\"Fetch\") {\n                Task { await fetch() }\n            }\n        }\n        .task { await tokenRefresh() }\n    }\n    \n    private func fetch() async {\n        result = try? await client.fetch()\n    }\n    \n    private func tokenRefresh() async {\n        while !Task.isCancelled {\n            let newToken = String((0..\u003c5).compactMap { _ in \"ABCDEF1234567890\".randomElement() })\n            print(\"New token: [\\(newToken)]\")\n            @KeychainStorage(\"token\") var token: String?\n            token = newToken // Set token\n            try? await Task.sleep(for: .seconds(5))\n        }\n    }\n}\n\n// MARK: - Preview\n#Preview {\n    ContentView()\n        .environment(\\.fetchClient, .live) // .environment(\\.fetchClient, .preview)\n}\n\n// MARK: - Entrypoint\n@main\nstruct KeychainStorageExampleApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n```\n\n#### Tests\n\nDue to Keychain access requirements, the tests for this package must run against a \"TestHost\" app (following [this tutorial](https://belief-driven-design.com/testing-swift-packages-with-a-test-host-56bf1/)). To run the tests:\n\n```zsh\nmake test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxhumber%2Fkeychainstoragekit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxhumber%2Fkeychainstoragekit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxhumber%2Fkeychainstoragekit/lists"}