XUtils

SecurePropertyStorage

Helps you define secure storages for your properties using Swift property wrappers.


🌟 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


Articles

  • coming soon...