在 SwiftUI 中使用带有最新 @Environment 的协议

bpi*_*ano 2 swift swiftui swiftui-environment

我正在使用 WWDC 2023 上推出的带有 SwiftUI 的最新观察框架。似乎新的@Environment属性包装器不允许使用协议。我的服务实现如下所示:

protocol UserServiceRepresentation {
    var user: User { get }
}

// Service implementation
@Observable
final class UserService: UserServiceRepresentable {
    private(set) var user: User

    func fetchUser() async { ... }
}

// Service mock
@Observable
final class UserServiceMock: UserServiceRepresentable {
    let user: User = .mock
}
Run Code Online (Sandbox Code Playgroud)

以前,可以使用@EnvironmentObject声明协议,以便您可以将模拟服务作为环境对象传递给视图。

struct UserView: View {
    // This is possible using EnvironmentObject.
    // But it require UserService to conforms to ObservableObject.
    @EnvironmentObject private var userService: UserService

    var body: some View { ... }
}

#Preview {
    UserView()
        .environmentObject(UserServiceMock()) // this allows your previews not to rely on production data.
}
Run Code Online (Sandbox Code Playgroud)

现在,对于@Environment,我不能做这样的事情:

struct UserView: View {
    // This is not possible
    // Throw compiler error: No exact matches in call to initializer
    @Environment(UserService.self) private var userService

    var body: some View { ... }
}
Run Code Online (Sandbox Code Playgroud)

我很乐意像这样注入我的模拟用户服务:

#Preview {
    UserView()
        .environment(UserServiceMock())
}
Run Code Online (Sandbox Code Playgroud)

我是否错过了什么或以错误的方式做事?新的属性包装器似乎@Environment不是为这种用途而构建的。我在网上找不到任何有关它的信息。在此先感谢您的帮助。

Swe*_*per 7

不,@Environment初始化器采用具体类型,即,并且继续使用和Observable没有任何问题。如果它没有坏,就不要修理它。EnvironmentObjectObservableObject

如果出于某种原因您确实喜欢@Observable,您可以创建一个存储用户服务的自定义环境密钥。环境键的值可以是存在协议类型。

struct UserServiceKey: EnvironmentKey {
    // you can also set the real user service as the default value
    static let defaultValue: any UserServiceRepresentation = UserServiceMock()
}

extension EnvironmentValues {
    var userService: any UserServiceRepresentation {
        get { self[UserServiceKey.self] }
        set { self[UserServiceKey.self] = newValue }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以调用Environment采用键路径的初始化程序。

@Environment(\.userService) var service
Run Code Online (Sandbox Code Playgroud)
.environment(\.userService, someService)
Run Code Online (Sandbox Code Playgroud)

请注意,如果 的实例UserServiceRepresentation不是Observable,则此操作将不起作用。要求Observable一致性可能会更好。

protocol UserServiceRepresentation: Observable
Run Code Online (Sandbox Code Playgroud)