将 StateObject 注入 SwiftUI 视图
Inject a StateObject into SwiftUI View
@StateObject 可以使用 Resolver 注入吗?
我有以下内容:
struct FooView: View {
@StateObject private var viewModel: FooViewModel
some code
}
protocol FooViewModel: ObservableObject {
var someValue: String { get }
func someRequest()
}
class FooViewModelImpl {
some code
}
我想使用 Resolver 将 FooViewModel 注入到 FooView 中,但一直在努力,因为 Resolver 想要使用 @Inject 注释,当然,我需要 @StateObject 注释,但我似乎无法同时使用两者。 @StateObject 是否无法使用 Resolver 等依赖注入框架进行注入?我还没有找到任何开发人员在这种方法中使用 DI 的示例。
不,@StateObject
是一个单独的事实来源,它不应该有任何其他依赖。传递一个对象,例如管理模型结构生命周期的对象,您可以使用 @ObservedObject
或 @EnvironmentObject
.
仅供参考,我们不在 SwiftUI 中使用视图模型对象。看到这个答案 "MVVM has no place in SwiftUI."
ObservableObject
是 Combine 框架的一部分,因此您通常只在需要将 Combine 管道的输出 assign
输出到 @Published
属性 时才使用它。在 SwiftUI 和 Swift 中,大多数时候你应该使用像结构这样的值类型。参见 Choosing Between Structures and Classes。我们使用 DynamicProperty
和 属性 包装器,如 @State
和 @Binding
来使我们的结构表现得像对象。
不确定解析器,但您可以使用以下方法将 VM 传递给 V。
导入 SwiftUI
class FooViewModel: ObservableObject {
@Published var counter: Int = 0
}
struct FooView: View {
@StateObject var vm: FooViewModel
var body: some View {
VStack {
Button {
vm.counter += 1
} label: {
Text("Increment")
}
}
}
}
struct ContentView: View {
var body: some View {
FooView(vm: FooViewModel())
}
}
如果你的 StateObject 有一个依赖项——而不是使用一个重量级的依赖注入框架——你可以利用 Swift 环境和一个超轻的“Reader Monad”来设置你的依赖注入state对象,基本实现一样,只是几行代码。
以下方法避免了在主体函数中设置 StateObject
的“黑客攻击”,这可能会导致 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”将在 body 函数内的 Button 的操作函数中注入视图模型,利用“Reader”,又名 .apply(environment: <dependency>)
。
另请注意,ViewModel 提供了一个函数
send(_ Event:) -> Reader<SecureStorage, Void>
其中 Event
只是一个 Enum
,它包含每个可能的 User Intent.
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”的设置方式。基本上很容易:
Reader 只包含一个函数:(E) -> A
,其中 E
是依赖项,A
是函数的结果(这里是 Void
)。
Reader 模式起初可能令人难以置信。但是,只要想一想 send(_:)
returns 一个函数 (E) -> Void
,其中 E 是安全存储依赖项,然后该函数只执行具有依赖项时需要执行的任何操作。事实上,“穷人”reader 只会return 这个函数,而不是“Monad”。成为 Monad 开启了以各种很酷的方式编写 Reader 的机会。
最小Reader单子:
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
Resolver 的最新版本支持 @InjectedObject
属性 ObservableObjects 的包装器。此包装器旨在用于 SwiftUI 视图并公开类似于 SwiftUI @ObservedObject 和 @EnvironmentObject 的可绑定对象。
我现在经常使用它,它的功能非常棒。
例如:
class AuthService: ObservableObject {
@Published var isValidated = false
}
class LoginViewModel: ObservableObject {
@InjectedObject var authService: AuthService
}
注意:依赖服务必须是ObservableObject类型。更新对象状态将触发视图更新。
@StateObject 可以使用 Resolver 注入吗?
我有以下内容:
struct FooView: View {
@StateObject private var viewModel: FooViewModel
some code
}
protocol FooViewModel: ObservableObject {
var someValue: String { get }
func someRequest()
}
class FooViewModelImpl {
some code
}
我想使用 Resolver 将 FooViewModel 注入到 FooView 中,但一直在努力,因为 Resolver 想要使用 @Inject 注释,当然,我需要 @StateObject 注释,但我似乎无法同时使用两者。 @StateObject 是否无法使用 Resolver 等依赖注入框架进行注入?我还没有找到任何开发人员在这种方法中使用 DI 的示例。
不,@StateObject
是一个单独的事实来源,它不应该有任何其他依赖。传递一个对象,例如管理模型结构生命周期的对象,您可以使用 @ObservedObject
或 @EnvironmentObject
.
仅供参考,我们不在 SwiftUI 中使用视图模型对象。看到这个答案 "MVVM has no place in SwiftUI."
ObservableObject
是 Combine 框架的一部分,因此您通常只在需要将 Combine 管道的输出 assign
输出到 @Published
属性 时才使用它。在 SwiftUI 和 Swift 中,大多数时候你应该使用像结构这样的值类型。参见 Choosing Between Structures and Classes。我们使用 DynamicProperty
和 属性 包装器,如 @State
和 @Binding
来使我们的结构表现得像对象。
不确定解析器,但您可以使用以下方法将 VM 传递给 V。
导入 SwiftUI
class FooViewModel: ObservableObject {
@Published var counter: Int = 0
}
struct FooView: View {
@StateObject var vm: FooViewModel
var body: some View {
VStack {
Button {
vm.counter += 1
} label: {
Text("Increment")
}
}
}
}
struct ContentView: View {
var body: some View {
FooView(vm: FooViewModel())
}
}
如果你的 StateObject 有一个依赖项——而不是使用一个重量级的依赖注入框架——你可以利用 Swift 环境和一个超轻的“Reader Monad”来设置你的依赖注入state对象,基本实现一样,只是几行代码。
以下方法避免了在主体函数中设置 StateObject
的“黑客攻击”,这可能会导致 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”将在 body 函数内的 Button 的操作函数中注入视图模型,利用“Reader”,又名 .apply(environment: <dependency>)
。
另请注意,ViewModel 提供了一个函数
send(_ Event:) -> Reader<SecureStorage, Void>
其中 Event
只是一个 Enum
,它包含每个可能的 User Intent.
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”的设置方式。基本上很容易:
Reader 只包含一个函数:(E) -> A
,其中 E
是依赖项,A
是函数的结果(这里是 Void
)。
Reader 模式起初可能令人难以置信。但是,只要想一想 send(_:)
returns 一个函数 (E) -> Void
,其中 E 是安全存储依赖项,然后该函数只执行具有依赖项时需要执行的任何操作。事实上,“穷人”reader 只会return 这个函数,而不是“Monad”。成为 Monad 开启了以各种很酷的方式编写 Reader 的机会。
最小Reader单子:
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
Resolver 的最新版本支持 @InjectedObject
属性 ObservableObjects 的包装器。此包装器旨在用于 SwiftUI 视图并公开类似于 SwiftUI @ObservedObject 和 @EnvironmentObject 的可绑定对象。
我现在经常使用它,它的功能非常棒。
例如:
class AuthService: ObservableObject { @Published var isValidated = false } class LoginViewModel: ObservableObject { @InjectedObject var authService: AuthService }
注意:依赖服务必须是ObservableObject类型。更新对象状态将触发视图更新。