π Features
All keys are hashed using SHA512 and all values are encrypted using AES-GCM to keep user information safe, auto*magic*ally. Symmetric key is stored in Keychain in a totally secure way.
@UserDefault
This property wrapper will store your property in UserDefaults using StoreKey
(any String
but i recommend you a String typed enum).
Optionally, you can assign a default value to the property that will be secure stored at initialization.
@UserDefault(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded
UserDefaultsStorage
is also available, a subclass of UserDefaults
with all the security provided by this library, where you can customize suite name.
@Keychain
This property wrapper will store your property in Keychain using StoreKey
.
@Keychain(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded
As UserDefaultsStorage
, KeychainStorage
is also available, where you can customize access, group and synchronize it with iCloud.
@Singleton
This property wrapper will store your property in a memory singleton, every property with the same wrapper and key can access or modify the value from wherever it is.
@Singleton(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded
As KeychainStorage
, SingletonStorage
is also available.
@Store
This is a custom wrapper, you can define your own Storage
protocol implementation.
@Store(<#YourStorage#>, <#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded
As InjectStorage
, DelegatedStorage
is also available with all the magic of this library.
Scope
Because 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.
@Inject(.instance)
var yourDependency: YourProtocol?
@UnwrappedInject(.instance)
var yourUnwrappedDependency: YourProtocol
@InjectWith and @UnwrappedInjectWith (click to expand)
Your dependency may need parameters when injecting, you can pass them with these property wrappers. Simply define a model with your dependency parameters and pass it. It will inject a new instance built with these parameters. ```swift @Register var yourDependency = { parameters in YourImplementation(parameters) as YourProtocol } @Inject(YourParameters()) var yourDependency: YourProtocol? @UnwrappedInject(YourParameters()) var yourUnwrappedDependency: YourProtocol ```Qualifiers (click to expand)
You 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`. For 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: ```swift protocol Animal { func sound() } @objc protocol Dog {} @objc protocol Cat {} ``` You can then define multiple classes that conforms `Animal` protocol and uses this qualifiers: ```swift class DogImplementation: Animal, Dog { func sound() { print("Woof!") } } class CatImplementation: Animal, Cat { func sound() { print("Meow!") } } ``` Both implementations of the class can now be `@Register`: ```swift @Register var registerDog: Animal = DogImplementation() @Register var registerCat: Animal = CatImplementation() ``` To inject one or the other implementation, simply add the qualifier(s) to your `@Inject`: ```swift @UnwrappedInject(Dog.self) var dog: Animal @UnwrappedInject(Cat.self) var cat: Animal dog.sound() // prints Woof! cat.sound() // prints Meow! ```Testing (click to expand)
One of the advantages of dependency injection is that the code can be easily testable with mock implementation. That 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. ```swift // App target class YourImplementation: YourProtocol {} @Register var yourDependency: YourProtocol = YourImplementation() @Inject var yourDependency: YourProtocol? ``` ```swift // Test target class YourMock: YourProtocol, Mock {} @Register var yourDependency: YourProtocol = YourMock() ```Groups (click to expand)
When you have **a lot** of dependencies in your app, you may want to optimize dependency resolution. You can group them using `@Register(group:)` and a `DependencyGroupKey`: ```swift @Register(group: <#DependencyGroupKey#>) var yourDependency: YourProtocol = YourImplementation() ``` `@Inject(group:)` will look for those dependencies only in that group: ```swift @Inject(group: <#DependencyGroupKey#>) var yourDependency: YourProtocol? ```π Examples
Talk is cheap. Show me the code.
// Securely stored in UserDefaults.
@UserDefault("username")
var username: String?
// Securely stored in Keychain.
@Keychain("password")
var password: String?
// Securely stored in a Singleton storage.
@Singleton("sessionToken")
var sessionToken: String?
// Securely stored in a Singleton storage.
// Always has a value, the stored or the default.
@UnwrappedSingleton("refreshToken")
var refreshToken: String = "B0610306-A33F"
struct User: Codable {
let username: String
let password: String?
let sessionToken: String?
}
// Codable model securely stored in UserDefaults.
@CodableUserDefault("user")
var user: User?
π Compatibility
- macOS 10.15+
- iOS 13.0+
- iPadOS 13.0+
- tvOS 13.0+
- watchOS 6.0+
- visionOS 1.0+
You can use the Swift Package Manager by declaring SecurePropertyStorage as a dependency in your Package.swift
file:
.package(url: "https://github.com/alexruperez/SecurePropertyStorage", from: "0.7.1")
By default, all property wrappers are installed and you can import
them, but if you want, you can install only some of them:
- UserDefault: @*UserDefault property wrappers.
- Keychain: @*Keychain property wrappers.
- Singleton: @*Singleton property wrappers.
- Storage: @*Store property wrappers.
- Inject: @*Inject property wrappers.
For more information, see the Swift Package Manager documentation.
Or you can use Carthage:
github "alexruperez/SecurePropertyStorage"
π» Etc.
- Featured in Dave Verwer’s iOS Dev Weekly - Issue 450, thanks Dave!
- Contributions are very welcome. Thanks Alberto Garcia and Chen!
- Attribution is appreciated (let’s spread the word!), but not mandatory.
π¨βπ» Author
Alex RupΓ©rez β @alexruperez β me@alexruperez.com