🔐 安全属性存储

使用 Swift 属性包装器 帮助你为属性定义安全存储。

Twitter Swift License Swift Package Manager Carthage Bitrise Maintainability Test Coverage codecov codebeat badge Quality Documentation

🌟 特性

所有键都使用 SHA512 进行哈希处理,所有值都使用 AES-GCM 进行加密,以确保用户信息安全,自动魔术般的完成。对称密钥以完全安全的方式存储在 Keychain 中。

🐒 基本用法

@UserDefault

此属性包装器将使用 StoreKey(任何 String 类型,但我建议使用 String 类型的枚举)将你的属性存储在 UserDefaults 中。你可以选择为属性分配一个默认值,该默认值将在初始化时进行安全存储。

@UserDefault(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

UserDefaultsStorage 也可用,它是 UserDefaults 的子类,具有此库提供的所有安全性,你可以在其中自定义套件名称。

@Keychain

此属性包装器将使用 StoreKey 将你的属性存储在 Keychain 中。

@Keychain(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

UserDefaultsStorage 一样,KeychainStorage 也可用,你可以在其中自定义访问权限、组并将它与 iCloud 同步。

@Singleton

此属性包装器将你的属性存储在内存 单例中,每个具有相同包装器和键的属性都可以从任何位置访问或修改该值。

@Singleton(<#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

KeychainStorage 一样,SingletonStorage 也可用。

@Inject

此属性包装器与 @Singleton 类似,但与 @Register 一起,它将注入你的依赖项。更多详细信息请参考 依赖注入用法 指南。

@Inject
var yourDependency: YourProtocol?

SingletonStorage 一样,InjectStorage 也可用。

@Store

这是一个自定义包装器,你可以定义自己的 Storage 协议实现。

@Store(<#YourStorage#>, <#StoreKey#>)
var yourProperty: YourType? = yourDefaultValueIfNeeded

InjectStorage 一样,DelegatedStorage 也可用,它具有此库的所有魔力。

🧙‍♂️ Codable 用法

如果你的属性符合 Codable 协议,只需将 Codable 关键字添加为属性包装器的前缀即可。

🥡 解包用法

为了避免不断解包你的属性,只需将 Unwrapped 关键字添加为属性包装器的前缀,分配一个默认值(除了 @UnwrappedInject 之外,这是强制性的),它将返回存储的值或默认值,但你的属性将始终存在为你服务。

🥡 + 🧙‍♂️ 组合用法

如果需要,你也可以组合之前的案例,请先解包。

💉 依赖注入用法

@Register (点击展开)

此属性包装器将注册你的依赖项的实现。在注入依赖项之前,在任何你想要的地方注册它们,但请确保只注册一次(除非你使用限定符),例如,在一个 Injector 类中。你可以通过协议注册,也可以直接使用你的类实现。

@Register
var yourDependency: YourProtocol = YourImplementation()

@Register
var yourDependency = YourImplementation()

你还可以定义一个构建你的依赖项的闭包。请记住,如果你要通过协议注入依赖项,请强制转换你的依赖项。

@Register
var yourDependency = {
    YourImplementation() as YourProtocol
}

@Register
var yourDependency = {
    YourImplementation()
}

你也可以仅在有人尝试注入你的依赖项但你尚未注册它们之后才注册它们,为此你可以使用错误闭包。

InjectStorage.standard.errorClosure = { error in
    if case InjectError.notFound = error {
        YourImplementation.register()
    }
}

你可以获得这种语法糖,因为你现在可以在函数参数中使用属性包装器。

static func register(@Register yourDependency: YourProtocol = YourImplementation()) {}
@Inject@UnwrappedInject (点击展开)

这些属性包装器注入你的依赖项 @Register 实现。

@Inject
var yourDependency: YourProtocol?

@Inject
var yourDependency: YourImplementation?

@UnwrappedInject
var yourUnwrappedDependency: YourProtocol

@UnwrappedInject
var yourUnwrappedDependency: YourImplementation

作用域

由于这些属性包装器的工作方式与 @Singleton 类似,因此默认作用域是 .singleton,但如果你在 @Register 上使用构建器闭包,你可以修改它们以注入单个实例。

@Inject(.instance)
var yourDependency: YourProtocol?

@UnwrappedInject(.instance)
var yourUnwrappedDependency: YourProtocol
@InjectWith@UnwrappedInjectWith (点击展开)

你的依赖项在注入时可能需要参数,你可以使用这些属性包装器传递它们。只需定义一个包含你的依赖项参数的模型并传递它即可。它将注入一个使用这些参数构建的新实例。

@Register
var yourDependency = { parameters in
    YourImplementation(parameters) as YourProtocol
}

@Inject(YourParameters())
var yourDependency: YourProtocol?

@UnwrappedInject(YourParameters())
var yourUnwrappedDependency: YourProtocol
限定符 (点击展开)

你可以使用 限定符 来提供特定依赖项的各种实现。限定符只是一个应用于 class@objc protocol

例如,你可以声明 DogCat 限定符协议并将它应用到另一个符合 Animal 协议的类。要声明此限定符,请使用以下代码

protocol Animal {
  func sound()
}

@objc protocol Dog {}

@objc protocol Cat {}

然后,你可以定义多个符合 Animal 协议并使用此限定符的类

class DogImplementation: Animal, Dog {
    func sound() { print("Woof!") }
}

class CatImplementation: Animal, Cat {
    func sound() { print("Meow!") }
}

该类的两个实现现在都可以 @Register

@Register
var registerDog: Animal = DogImplementation()

@Register
var registerCat: Animal = CatImplementation()

要注入其中一个或另一个实现,只需将限定符添加到你的 @Inject

@UnwrappedInject(Dog.self)
var dog: Animal

@UnwrappedInject(Cat.self)
var cat: Animal

dog.sound() // prints Woof!
cat.sound() // prints Meow!
测试 (点击展开)

依赖注入的优势之一是可以使用模拟实现轻松测试代码。这就是为什么有一个 Mock 限定符具有高于一切的优先级,因此你可以将你的依赖项定义在应用程序中,并通过简单地添加此限定符在测试目标中创建你的模拟。

// App target

class YourImplementation: YourProtocol {}

@Register
var yourDependency: YourProtocol = YourImplementation()

@Inject
var yourDependency: YourProtocol?
// Test target

class YourMock: YourProtocol, Mock {}

@Register
var yourDependency: YourProtocol = YourMock()
(点击展开)

当你的应用程序中有很多依赖项时,你可能想要优化依赖项解析。

你可以使用 @Register(group:)DependencyGroupKey 对它们进行分组

@Register(group: <#DependencyGroupKey#>)
var yourDependency: YourProtocol = YourImplementation()

@Inject(group:) 将仅在该组中查找这些依赖项

@Inject(group: <#DependencyGroupKey#>)
var yourDependency: YourProtocol?

👀 示例

空谈无益,放码过来。

    // 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?

🛠 兼容性

⚙️ 安装

你可以使用 Swift Package Manager,方法是在你的 Package.swift 文件中将 SecurePropertyStorage 声明为依赖项

.package(url: "https://github.com/alexruperez/SecurePropertyStorage", from: "0.7.1")

默认情况下,所有属性包装器都已安装,你可以 import 它们,但如果你愿意,你可以只安装其中的一些

有关更多信息,请参阅 Swift Package Manager 文档

或者你可以使用 Carthage

github "alexruperez/SecurePropertyStorage"

🍻 等等。

👨‍💻 作者

Alex Rupérez – @alexruperezme@alexruperez.com

👮‍♂️ 许可

SecurePropertyStorage 在 MIT 许可下可用。有关更多信息,请参见 LICENSE 文件。