PropertyWrappers 和协议声明? [英] PropertyWrappers and protocol declaration?

查看:35
本文介绍了PropertyWrappers 和协议声明?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 Xcode 11 beta 6,我正在尝试使用 @Published 为具有属性的类型声明一个协议(但这个问题可以推广到任何 我猜是 PropertyWrapper).

Using Xcode 11 beta 6, I am trying to declare a protocol for a type with properties using @Published (but this question can be generalized to any PropertyWrapper I guess).

final class DefaultWelcomeViewModel: WelcomeViewModel & ObservableObject {
    @Published var hasAgreedToTermsAndConditions = false
}

我试图声明:

protocol WelcomeViewModel {
    @Published var hasAgreedToTermsAndConditions: Bool { get }
}

导致编译错误:在协议中声明的属性hasAgreedToTermsAndConditions"不能有包装器

所以我试着把它改成:

protocol WelcomeViewModel {
    var hasAgreedToTermsAndConditions: Published<Bool> { get }
}

并尝试

编译不出来,DefaultWelcomeViewModel不符合协议,好吧,嗯,我不能用Published那我们试试吧!

Which does not compile, DefaultWelcomeViewModel does not conform to protocol, okay, so hmm, I cannot using Published<Bool> then, let's try it!

struct WelcomeScreen<ViewModel> where ViewModel: WelcomeViewModel & ObservableObject {
    @EnvironmentObject private var viewModel: ViewModel

    var body: some View {
        // Compilation error: `Cannot convert value of type 'Published<Bool>' to expected argument type 'Binding<Bool>'`
        Toggle(isOn: viewModel.hasAgreedToTermsAndConditions) {
            Text("I agree to the terms and conditions")
        }
    }
}

// MARK: - ViewModel
protocol WelcomeViewModel {
    var hasAgreedToTermsAndConditions: Published<Bool> { get }
}

final class DefaultWelcomeViewModel: WelcomeViewModel & ObservableObject {
    var hasAgreedToTermsAndConditions = Published<Bool>(initialValue: false)
}

这导致 Toggle 上的编译错误:Cannot convert value of type 'Published'到预期的参数类型 'Binding'.

Which results in the compilation error on the Toggle: Cannot convert value of type 'Published<Bool>' to expected argument type 'Binding<Bool>'.

推荐答案

我认为您提出的明确问题与您试图解决的问题不同,但我会尽力为两者提供帮助.

I think the explicit question you're asking is different from the problem you are trying to solve, but I'll try to help with both.

首先,您已经意识到不能在协议中声明属性包装器.这是因为属性包装器声明在编译时被合成为三个单独的属性,这不适用于抽象类型.

First, you've already realized you cannot declare a property wrapper inside a protocol. This is because property wrapper declarations get synthesized into three separate properties at compile-time, and this would not be appropriate for an abstract type.

因此,为了回答您的问题,您不能在协议内显式声明属性包装器,但您可以为属性包装器的每个合成属性创建单独的属性要求,例如:

So to answer your question, you cannot explicitly declare a property wrapper inside of a protocol, but you can create individual property requirements for each of the synthesized properties of a property wrapper, for example:

protocol WelcomeViewModel {
    var hasAgreed: Bool { get }
    var hasAgreedPublished: Published<Bool> { get }
    var hasAgreedPublisher: Published<Bool>.Publisher { get }
}

final class DefaultWelcomeViewModel: ObservableObject, WelcomeViewModel {
    @Published var hasAgreed: Bool = false
    var hasAgreedPublished: Published<Bool> { _hasAgreed }
    var hasAgreedPublisher: Published<Bool>.Publisher { $hasAgreed }
}

如您所见,两个属性(_hasAgreed$hasAgreed)已经由具体类型的属性包装器合成,我们可以简单地从计算我们的协议所需的属性.

As you can see, two properties (_hasAgreed and $hasAgreed) have been synthesized by the property wrapper on the concrete type, and we can simply return these from computed properties required by our protocol.

现在我相信我们的 Toggle 有一个完全不同的问题,编译器很高兴地提醒我们:

Now I believe we have a different problem entirely with our Toggle which the compiler is happily alerting us to:

无法将已发布"类型的值转换为预期的参数类型绑定"

Cannot convert value of type 'Published' to expected argument type 'Binding'

这个错误也很简单.Toggle 需要一个 Binding,但我们试图提供一个 Published,它不是同一类型.幸运的是,我们选择使用 @EnvironmentObject,这使我们能够使用 viewModel 上的投影值"来获得 Binding到视图模型的属性.这些值是使用符合条件的属性包装器上的 $ 前缀访问的.事实上,我们已经使用 hasAgreedPublisher 属性完成了上述操作.

This error is straightforward as well. Toggle expects a Binding<Bool>, but we are trying to provide a Published<Bool> which is not the same type. Fortunately, we have chosen to use an @EnvironmentObject, and this enables us to use the "projected value" on our viewModel to obtain a Binding to a property of the view model. These values are accessed using the $ prefix on an eligible property wrapper. Indeed, we have already done this above with the hasAgreedPublisher property.

所以让我们更新我们的 Toggle 以使用 Binding:

So let's update our Toggle to use a Binding:

struct WelcomeView: View {
    @EnvironmentObject var viewModel: DefaultWelcomeViewModel

    var body: some View {
        Toggle(isOn: $viewModel.hasAgreed) {
            Text("I agree to the terms and conditions")
        }
    }
}

通过在 viewModel 前面加上 $,我们可以访问一个在我们的视图模型上支持动态成员查找"的对象,以获得一个 Binding 到视图模型的成员.

By prefixing viewModel with $, we get access to an object that supports "dynamic member lookup" on our view model in order to obtain a Binding to a member of the view model.

这篇关于PropertyWrappers 和协议声明?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆