PropertyWrappers 和协议声明? [英] PropertyWrappers and protocol declaration?
问题描述
使用 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
.
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屋!