如果你的 StateObject 有一个依赖项——而不是使用一个重量级的依赖注入框架——你可以利用 Swift 环境和一个超轻量级的“Reader Monad”来设置你的依赖注入状态对象,并且基本上实现相同,只是几行代码。
以下方法避免了在 body 函数中设置 StateObject 的“hack”,这可能导致 StateObject 的意外行为。在创建视图时,依赖对象将使用默认初始化程序完全初始化一次且仅一次。依赖注入稍后发生,当依赖对象的函数将被使用时:
给定一个具体的依赖关系,比如SecureStore 符合协议,比如SecureStorage:
extension SecureStore: SecureStorage {}
定义环境密钥并设置默认的具体“SecureStore”:
private struct SecureStoreKey: EnvironmentKey {
static let defaultValue: SecureStorage =
SecureStore(
accessGroup: "myAccessGroup"
accessible: .whenPasscodeSetThisDeviceOnly
)
}
extension EnvironmentValues {
var secureStore: SecureStorage {
get { self[SecureStoreKey.self] }
set { self[SecureStoreKey.self] = newValue }
}
}
在其他地方,您有一个显示来自安全存储的一些凭据的视图,该访问将由视图模型处理,该模型设置为@StateObject:
struct CredentialView: View {
@Environment(\.secureStore) private var secureStore: SecureStorage
@StateObject private var viewModel = CredentialViewModel()
@State private var username: String = "test"
@State private var password: String = "test"
var body: some View {
Form {
Section(header: Text("Credentials")) {
TextField("Username", text: $username)
.keyboardType(.default)
.autocapitalization(.none)
.disableAutocorrection(true)
SecureField("Password", text: $password)
}
Section {
Button(action: {
self.viewModel.send(.submit(
username: username,
password: password
))
.apply(e: secureStore)
}, label: {
Text("Submitt")
.frame(minWidth: 0, maxWidth: .infinity)
})
}
}
.onAppear {
self.viewModel.send(.readCredential)
.apply(e: secureStore)
}
.onReceive(self.viewModel.$viewState) { viewState in
print("onChange: new: \(viewState.credential)")
username = viewState.credential.username
password = viewState.credential.password
}
}
}
这里有趣的部分是在何时何地执行依赖注入:
self.viewModel.send(.submit(...))
.apply(e: secureStore) // apply the dependency
在这里,依赖“secureStore”将被注入到视图模型中的主体函数内按钮的动作函数中,利用“阅读器”,又名.apply(environment: <dependency>)。
还要注意 ViewModel 提供了一个函数
send(_ Event:) -> Reader<SecureStorage, Void>
其中Event 只是一个Enum,它为每个可能的用户意图提供了案例。
final class CredentialViewModel: ObservableObject {
struct ViewState: Equatable {
var credential: Credential =
.init(username: "", password: "")
}
enum Event {
case submit(username: String, password: String)
case readCredential
case deleteCredential
case confirmAlert
}
@Published var viewState: ViewState = .init()
func send(_ event: Event) -> Reader<SecureStorage, Void>
...
然后您的视图模型可以实现send(_:) 函数,如下所示:
func send(_ event: Event) -> Reader<SecureStorage, Void> {
Reader { secureStore in
switch event {
case .readCredential:
...
case .submit(let username, let password):
secureStore.set(
item: Credential(
username: username,
password: password
),
key: "credential"
)
case .deleteCredential:
...
}
}
注意“阅读器”的设置方式。基本上很容易:
Reader 只保存一个函数:(E) -> A,其中E 是依赖项,A 是函数的结果(此处为 Void)。
Reader 模式起初可能令人难以置信。但是,想想send(_:) 返回一个函数(E) -> Void,其中 E 是安全存储依赖项,然后该函数只是在有依赖项时做任何需要做的事情。事实上,“穷人”阅读器只会返回这个函数,而不是“Monad”。成为 Monad 为以各种酷炫的方式编写 Reader 提供了机会。
Minimal Reader Monad:
struct Reader<E, A> {
let g: (E) -> A
init(g: @escaping (E) -> A) {
self.g = g
}
func apply(e: E) -> A {
return g(e)
}
func map<B>(f: @escaping (A) -> B) -> Reader<E, B> {
return Reader<E, B>{ e in f(self.g(e)) }
}
func flatMap<B>(f: @escaping (A) -> Reader<E, B>) -> Reader<E, B> {
return Reader<E, B>{ e in f(self.g(e)).g(e) }
}
}
有关 Reader Monad 的更多信息:
https://medium.com/@foolonhill/techniques-for-a-functional-dependency-injection-in-swift-b9a6143634ab