{"id":1939,"url":"https://github.com/alexruperez/SecurePropertyStorage","last_synced_at":"2025-08-06T14:31:40.502Z","repository":{"id":38842394,"uuid":"249269523","full_name":"alexruperez/SecurePropertyStorage","owner":"alexruperez","description":"Helps you define secure storages for your properties using Swift property wrappers.","archived":false,"fork":false,"pushed_at":"2024-05-25T03:52:32.000Z","size":32504,"stargazers_count":471,"open_issues_count":0,"forks_count":20,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-05-28T05:02:59.997Z","etag":null,"topics":["dependency-injection","keychain","property-wrappers","singleton","swift","userdefaults"],"latest_commit_sha":null,"homepage":"https://alexruperez.github.io/SecurePropertyStorage","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexruperez.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-22T20:47:12.000Z","updated_at":"2024-05-29T23:40:37.971Z","dependencies_parsed_at":"2024-05-29T23:50:45.004Z","dependency_job_id":null,"html_url":"https://github.com/alexruperez/SecurePropertyStorage","commit_stats":{"total_commits":43,"total_committers":3,"mean_commits":"14.333333333333334","dds":0.06976744186046513,"last_synced_commit":"c55f0462096552d5d2771aa8930498c0488a0a94"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexruperez%2FSecurePropertyStorage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexruperez%2FSecurePropertyStorage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexruperez%2FSecurePropertyStorage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexruperez%2FSecurePropertyStorage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexruperez","download_url":"https://codeload.github.com/alexruperez/SecurePropertyStorage/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":215780273,"owners_count":15929791,"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","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":["dependency-injection","keychain","property-wrappers","singleton","swift","userdefaults"],"created_at":"2024-01-05T20:15:59.450Z","updated_at":"2025-08-06T14:31:40.483Z","avatar_url":"https://github.com/alexruperez.png","language":"Swift","readme":"# 🔐 Secure Property Storage\n\u003e Helps you define secure storages for your properties using Swift *property wrappers*.\n\n[![Twitter](https://img.shields.io/badge/me-%40alexruperez-blue)](http://alexruperez.com)\n[![Swift](https://img.shields.io/badge/swift-6-orange)](https://swift.org)\n[![License](https://img.shields.io/github/license/alexruperez/SecurePropertyStorage)](LICENSE)\n[![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager)\n[![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Bitrise](https://app.bitrise.io/app/4fed1af31836d3bc/status.svg?token=bYImtoKj0hxqCxnORhdgyg\u0026branch=master)](https://app.bitrise.io/app/4fed1af31836d3bc)\n[![Maintainability](https://api.codeclimate.com/v1/badges/bbf38ddca9a26703cefd/maintainability)](https://codeclimate.com/github/alexruperez/SecurePropertyStorage/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/bbf38ddca9a26703cefd/test_coverage)](https://codeclimate.com/github/alexruperez/SecurePropertyStorage/test_coverage)\n[![codecov](https://codecov.io/gh/alexruperez/SecurePropertyStorage/graph/badge.svg?token=T3NNtLnmsA)](https://codecov.io/gh/alexruperez/SecurePropertyStorage)\n[![codebeat badge](https://codebeat.co/badges/ee2372f0-2188-43c6-bf7b-f6860834096c)](https://codebeat.co/projects/github-com-alexruperez-securepropertystorage-master)\n[![Quality](https://api.codacy.com/project/badge/Grade/53a23dd2feca4b7ca8357c918c7d49c9)](https://app.codacy.com/gh/alexruperez/SecurePropertyStorage/dashboard)\n[![Documentation](https://alexruperez.github.io/SecurePropertyStorage/badge.svg)](https://alexruperez.github.io/SecurePropertyStorage)\n\n## 🌟 Features\n\nAll keys are hashed using [SHA512](https://en.wikipedia.org/wiki/SHA-2) and all values are encrypted using [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)-[GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) to keep user information safe, auto*magic*ally.\nSymmetric key is stored in Keychain in a totally secure way.\n\n## 🐒 Basic usage\n\n### @UserDefault\n\nThis property wrapper will store your property in [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) using `StoreKey` (any `String` but i recommend you a String typed enum).\nOptionally, you can assign a default value to the property that will be secure stored at initialization.\n\n```swift\n@UserDefault(\u003c#StoreKey#\u003e)\nvar yourProperty: YourType? = yourDefaultValueIfNeeded\n```\n\n[`UserDefaultsStorage`](Sources/UserDefault/UserDefaultsStorage.swift) is also available, a subclass of [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults) with all the security provided by this library, where you can customize suite name.\n\n### @Keychain\n\nThis property wrapper will store your property in [Keychain](https://developer.apple.com/documentation/security/keychain_services) using `StoreKey`.\n\n```swift\n@Keychain(\u003c#StoreKey#\u003e)\nvar yourProperty: YourType? = yourDefaultValueIfNeeded\n```\n\nAs `UserDefaultsStorage`, [`KeychainStorage`](Sources/Keychain/KeychainStorage.swift) is also available, where you can customize access, group and synchronize it with iCloud.\n\n### @Singleton\n\nThis property wrapper will store your property in a memory [singleton](https://en.wikipedia.org/wiki/Singleton_pattern),  every property with the same wrapper and key can access or modify the value from wherever it is.\n\n```swift\n@Singleton(\u003c#StoreKey#\u003e)\nvar yourProperty: YourType? = yourDefaultValueIfNeeded\n```\n\nAs `KeychainStorage`, [`SingletonStorage`](Sources/Singleton/SingletonStorage.swift) is also available.\n\n### @Inject\n\nThis property wrapper is similar to `@Singleton` but, together with `@Register`, will inject your dependencies. More details in [Dependency Injection usage](#-dependency-injection-usage) guide.\n\n```swift\n@Inject\nvar yourDependency: YourProtocol?\n```\n\nAs `SingletonStorage`, [`InjectStorage`](Sources/Inject/InjectStorage.swift) is also available.\n\n### @Store\n\nThis is a custom wrapper, you can define your own [`Storage`](Sources/Storage/Storage.swift) protocol implementation.\n\n```swift\n@Store(\u003c#YourStorage#\u003e, \u003c#StoreKey#\u003e)\nvar yourProperty: YourType? = yourDefaultValueIfNeeded\n```\n\nAs `InjectStorage`, [`DelegatedStorage`](Sources/Storage/DelegatedStorage.swift) is also available with all the magic of this library.\n\n## 🧙‍♂️ Codable usage\n\nIf your property conforms [`Codable`](https://developer.apple.com/documentation/swift/codable) protocol, just add `Codable` keyword as prefix of your property wrapper.\n\n- **@CodableUserDefault**\n- **@CodableKeychain**\n- **@CodableSingleton**\n- **@CodableStore**\n\n## 🥡 Unwrapped usage\n\nTo avoid continually unwrapping your property, just add `Unwrapped` keyword as prefix of your property wrapper, assign a default value (mandatory except for `@UnwrappedInject`), and it will return stored value or default value, but your property will always be there for you.\n\n- **@UnwrappedUserDefault**\n- **@UnwrappedKeychain**\n- **@UnwrappedSingleton**\n- **@UnwrappedInject**\n- **@UnwrappedStore**\n\n## 🥡 + 🧙‍♂️ Combo usage\n\nYou can also combine previous cases in case you need it, unwrapped first please.\n\n- **@UnwrappedCodableUserDefault**\n- **@UnwrappedCodableKeychain**\n- **@UnwrappedCodableSingleton**\n- **@UnwrappedCodableStore**\n\n## 💉 Dependency Injection usage\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e@Register\u003c/b\u003e (\u003ci\u003eclick to expand\u003c/i\u003e)\u003c/summary\u003e\n\nThis property wrapper will register the implementations of your dependencies.\nRegister them wherever you want before inject it, but be sure to do it only once (except if you use qualifiers), for example, in an `Injector` class.\nYou can register through a protocol or directly using your class implementation.\n\n```swift\n@Register\nvar yourDependency: YourProtocol = YourImplementation()\n\n@Register\nvar yourDependency = YourImplementation()\n```\n\nYou can also define a closure that builds your dependency.\nJust remember cast your dependency if you are going to inject it through a protocol.\n\n```swift\n@Register\nvar yourDependency = {\n    YourImplementation() as YourProtocol\n}\n\n@Register\nvar yourDependency = {\n    YourImplementation()\n}\n```\n\nYou can also register your dependencies only after the moment someone tries to inject them and you haven't registered them yet,\nfor this you can use the error closure.\n\n```swift\nInjectStorage.standard.errorClosure = { error in\n    if case InjectError.notFound = error {\n        YourImplementation.register()\n    }\n}\n```\n\nYou can get this syntactic sugar because you can now use property wrappers in function parameters.\n\n```swift\nstatic func register(@Register yourDependency: YourProtocol = YourImplementation()) {}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e@Inject\u003c/b\u003e and \u003cb\u003e@UnwrappedInject\u003c/b\u003e (\u003ci\u003eclick to expand\u003c/i\u003e)\u003c/summary\u003e\n\nThese property wrappers injects your dependencies `@Register` implementations.\n\n```swift\n@Inject\nvar yourDependency: YourProtocol?\n\n@Inject\nvar yourDependency: YourImplementation?\n\n@UnwrappedInject\nvar yourUnwrappedDependency: YourProtocol\n\n@UnwrappedInject\nvar yourUnwrappedDependency: YourImplementation\n```\n\n#### Scope\n\nBecause these property wrappers works similarly to `@Singleton`, the default scope is `.singleton`, but if you use builder closures on `@Register`, you can modify them to inject a single instance.\n\n```swift\n@Inject(.instance)\nvar yourDependency: YourProtocol?\n\n@UnwrappedInject(.instance)\nvar yourUnwrappedDependency: YourProtocol\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e@InjectWith\u003c/b\u003e and \u003cb\u003e@UnwrappedInjectWith\u003c/b\u003e (\u003ci\u003eclick to expand\u003c/i\u003e)\u003c/summary\u003e\n\nYour dependency may need parameters when injecting, you can pass them with these property wrappers.\nSimply define a model with your dependency parameters  and pass it.\nIt will inject a new instance built with these parameters.\n\n```swift\n@Register\nvar yourDependency = { parameters in\n    YourImplementation(parameters) as YourProtocol\n}\n\n@Inject(YourParameters())\nvar yourDependency: YourProtocol?\n\n@UnwrappedInject(YourParameters())\nvar yourUnwrappedDependency: YourProtocol\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eQualifiers\u003c/b\u003e (\u003ci\u003eclick to expand\u003c/i\u003e)\u003c/summary\u003e\n\nYou can use [qualifiers](https://javaee.github.io/tutorial/cdi-basic006.html) to provide various implementations of a particular dependency. A qualifier is just a `@objc protocol` that you apply to a `class`.\n\nFor example, you could declare `Dog` and `Cat` qualifier protocols and apply it to another class that conforms `Animal` protocol. To declare this qualifier, use the following code:\n\n```swift\nprotocol Animal {\n  func sound()\n}\n\n@objc protocol Dog {}\n\n@objc protocol Cat {}\n```\n\nYou can then define multiple classes that conforms `Animal` protocol and uses this qualifiers:\n\n```swift\nclass DogImplementation: Animal, Dog {\n    func sound() { print(\"Woof!\") }\n}\n\nclass CatImplementation: Animal, Cat {\n    func sound() { print(\"Meow!\") }\n}\n```\n\nBoth implementations of the class can now be `@Register`:\n\n```swift\n@Register\nvar registerDog: Animal = DogImplementation()\n\n@Register\nvar registerCat: Animal = CatImplementation()\n```\n\nTo inject one or the other implementation, simply add the qualifier(s) to your `@Inject`:\n\n```swift\n@UnwrappedInject(Dog.self)\nvar dog: Animal\n\n@UnwrappedInject(Cat.self)\nvar cat: Animal\n\ndog.sound() // prints Woof!\ncat.sound() // prints Meow!\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eTesting\u003c/b\u003e (\u003ci\u003eclick to expand\u003c/i\u003e)\u003c/summary\u003e\n\nOne of the advantages of dependency injection is that the code can be easily testable with mock implementation.\nThat is why there is a `Mock` qualifier that has priority over all, so you can have your dependencies defined in the app and create your mock in the test target simply by adding this qualifier.\n\n```swift\n// App target\n\nclass YourImplementation: YourProtocol {}\n\n@Register\nvar yourDependency: YourProtocol = YourImplementation()\n\n@Inject\nvar yourDependency: YourProtocol?\n```\n\n```swift\n// Test target\n\nclass YourMock: YourProtocol, Mock {}\n\n@Register\nvar yourDependency: YourProtocol = YourMock()\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eGroups\u003c/b\u003e (\u003ci\u003eclick to expand\u003c/i\u003e)\u003c/summary\u003e\n\nWhen you have **a lot** of dependencies in your app, you may want to optimize dependency resolution.\n\nYou can group them using `@Register(group:)` and a `DependencyGroupKey`:\n\n```swift\n@Register(group: \u003c#DependencyGroupKey#\u003e)\nvar yourDependency: YourProtocol = YourImplementation()\n```\n\n`@Inject(group:)` will look for those dependencies only in that group:\n\n```swift\n@Inject(group: \u003c#DependencyGroupKey#\u003e)\nvar yourDependency: YourProtocol?\n```\n\u003c/details\u003e\n\n## 🔒 Concurrency and `Sendable`\n\nThis library is designed with Swift's modern concurrency features in mind. Access to the underlying storage mechanisms (UserDefaults, Keychain, etc.) is serialized through a global actor (`StorageActor`), preventing data races during read and write operations performed by the library itself.\n\nHowever, for your application to be fully concurrency-safe when using this library, it is crucial that **the types you store and retrieve are `Sendable`**.\n\n*   **Generic Types**: When using generic property wrappers or methods like `@Store var item: MyType?`, `storage.value(forKey: \"key\") as? MyType`, or `storage.decodable(forKey: \"key\") as? MyDecodableType`, ensure that `MyType` and `MyDecodableType` conform to the `Sendable` protocol.\n*   **Object Archiving**: If you use methods that rely on `NSKeyedArchiver` (such as `storage.set(object: myNSObject, forKey: \"key\")`), the class `myNSObject` should conform to `NSSecureCoding` for security and also be `Sendable` for concurrency safety.\n*   **Why is `Sendable` important?**: While the library ensures that the act of storing or retrieving data is safe, if the data itself (e.g., a class instance) is not `Sendable`, then passing it across different concurrent tasks or actors after retrieval can lead to data races if the data is mutable. Conforming to `Sendable` helps Swift enforce that such types can be safely shared.\n\nEnabling strict concurrency checking in your project's build settings (e.g., \"Strict Concurrency Checking\" set to \"Complete\" in Xcode) is highly recommended to help identify potential concurrency issues at compile time.\n\n\n## 👀 Examples\n\n\u003e Talk is cheap. Show me the code.\n\n```swift\n    // Securely stored in UserDefaults.\n    @UserDefault(\"username\")\n    var username: String?\n\n    // Securely stored in Keychain.\n    @Keychain(\"password\")\n    var password: String?\n\n    // Securely stored in a Singleton storage.\n    @Singleton(\"sessionToken\")\n    var sessionToken: String?\n\n    // Securely stored in a Singleton storage.\n    // Always has a value, the stored or the default.\n    @UnwrappedSingleton(\"refreshToken\")\n    var refreshToken: String = \"B0610306-A33F\"\n\n    struct User: Codable {\n        let username: String\n        let password: String?\n        let sessionToken: String?\n    }\n\n    // Codable model securely stored in UserDefaults.\n    @CodableUserDefault(\"user\")\n    var user: User?\n```\n\n## 🛠 Compatibility\n\n- macOS 11.5+\n- iOS 13.0+\n- iPadOS 13.0+\n- tvOS 13.0+\n- watchOS 6.0+\n- visionOS 1.0+\n\n## ⚙️ Installation\n\n#### You can use the [Swift Package Manager](https://github.com/apple/swift-package-manager) by declaring SecurePropertyStorage as a dependency in your `Package.swift` file:\n\n```swift\n.package(url: \"https://github.com/alexruperez/SecurePropertyStorage\", from: \"0.7.1\")\n```\n\nBy default, all property wrappers are installed and you can `import` them, but if you want, you can install only some of them:\n\n- **UserDefault**: @*UserDefault property wrappers.\n- **Keychain**: @*Keychain property wrappers.\n- **Singleton**: @*Singleton property wrappers.\n- **Storage**: @*Store property wrappers.\n- **Inject**: @*Inject property wrappers.\n\n*For more information, see [the Swift Package Manager documentation](https://github.com/apple/swift-package-manager/tree/master/Documentation).*\n\n#### Or you can use [Carthage](https://github.com/Carthage/Carthage):\n\n```ogdl\ngithub \"alexruperez/SecurePropertyStorage\"\n```\n\n## 🍻 Etc.\n\n- Featured in [Dave Verwer](https://twitter.com/daveverwer)'s iOS Dev Weekly - [Issue 450](https://iosdevweekly.com/issues/450?#ll98q5s), thanks Dave!\n- Contributions are very welcome. Thanks [Alberto Garcia](https://github.com/AlbGarciam), [Manu](https://github.com/Manu-Globant) and [Chen](https://github.com/qchenqizhi)!\n- Attribution is appreciated (let's spread the word!), but not mandatory.\n\n## 👨‍💻 Author\n\nAlex Rupérez – [@alexruperez](https://twitter.com/alexruperez) – me@alexruperez.com\n\n## 👮‍♂️ License\n\n*SecurePropertyStorage* is available under the MIT license. See the [LICENSE](LICENSE) file for more info.\n","funding_links":[],"categories":["Security","Libs","Awesome Repositories","Swift","Security [🔝](#readme)"],"sub_categories":["Unofficial","Security"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexruperez%2FSecurePropertyStorage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexruperez%2FSecurePropertyStorage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexruperez%2FSecurePropertyStorage/lists"}