[英]Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?
Why does the following code produce an error?为什么下面的代码会产生错误?
protocol ProtocolA {
var someProperty: ProtocolB { get }
}
protocol ProtocolB {}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA { // Type 'SomeClass' does not conform to protocol 'ProtocolA'
var someProperty: ConformsToB
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
}
The answer in this similar question makes sense.这个类似问题的答案是有道理的。 However, in my example, the property is get-only.
但是,在我的示例中,该属性是 get-only。 Why shouldn't this work?
为什么不应该这样做? Is it a shortcoming of Swift, or is there some reason this makes sense?
是 Swift 的缺点,还是有什么理由说得通?
There's no real reason why this shouldn't be possible, a read-only property requirement can be covariant, as returning a ConformsToB
instance from a property typed as ProtocolB
is perfectly legal.没有真正的理由为什么这不应该是不可能的,只读属性要求可以是协变的,因为从类型为
ProtocolB
的属性返回ConformsToB
实例是完全合法的。
Swift just currently doesn't support it. Swift 目前不支持它。 In order to do so, the compiler would have to generate a thunk between the protocol witness table and conforming implementation in order to perform the necessary type-conversion(s).
为此,编译器必须在协议见证表和符合要求的实现之间生成一个 thunk ,以便执行必要的类型转换。 For example, a
ConformsToB
instance would need to be boxed in an existential container in order to be typed as ProtocolB
(and there's no way the caller can do this, as it might not know anything about the implementation being called).例如,一个
ConformsToB
实例需要被装箱到一个存在容器中才能被输入为ProtocolB
(并且调用者无法做到这一点,因为它可能对被调用的实现一无所知)。
But again, there's no reason why the compiler shouldn't be able to do this.但同样,编译器没有理由不能这样做。 There are multiple bug reports open over this, this one which is specific to read-only property requirements, and this general one , in which Slava Pestov, a member of the Swift team, says:
有多个错误报告就此打开,这个是特定于只读属性要求的,还有这个通用的,其中 Swift 团队的成员 Slava Pestov 说:
[...] we want protocol witnesses and method overrides in every case where a function conversion is allowed
[...] 在允许函数转换的每种情况下,我们都需要协议见证和方法覆盖
So it definitely looks like something the Swift team are looking to implement in a future version of the language.所以它看起来肯定是 Swift 团队希望在该语言的未来版本中实现的东西。
In the mean time however, as @BallpointBen says , one workaround is to use an associatedtype
:然而,与此同时,正如@BallpointBen 所说,一种解决方法是使用
associatedtype
:
protocol ProtocolA {
// allow the conforming type to satisfy this with a concrete type
// that conforms to ProtocolB.
associatedtype SomeProperty : ProtocolB
var someProperty: SomeProperty { get }
}
protocol ProtocolB {}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA {
// implicitly satisfy the associatedtype with ConformsToB.
var someProperty: ConformsToB
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
}
But this is quite unsatisfactory, as it means that ProtocolA
is no longer usable as a type (because it has associatedtype
requirements).但这很不令人满意,因为这意味着
ProtocolA
不再可用作类型(因为它具有associatedtype
类型要求)。 It also changes what the protocol says.它还改变了协议所说的内容。 Originally it said that
someProperty
could return anything that conformed to ProtocolB
– now it says that an implementation of someProperty
deals with just one specific concrete type that conforms to ProtocolB
.最初它说
someProperty
可以返回任何符合ProtocolB
东西——现在它说someProperty
的实现只处理一种符合ProtocolB
特定具体类型。
Another workaround is just to define a dummy property in order to satisfy the protocol requirement:另一种解决方法是定义一个虚拟属性以满足协议要求:
protocol ProtocolA {
var someProperty: ProtocolB { get }
}
protocol ProtocolB {}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA {
// dummy property to satisfy protocol conformance.
var someProperty: ProtocolB {
return actualSomeProperty
}
// the *actual* implementation of someProperty.
var actualSomeProperty: ConformsToB
init(someProperty: ConformsToB) {
self.actualSomeProperty = someProperty
}
}
Here we're essentially writing the thunk for the compiler – but it's also not particularly nice as it adds a needless property to the API.在这里,我们实际上是在为编译器编写 thunk——但它也不是特别好,因为它为 API 添加了一个不必要的属性。
In addition to Harmish's great response, if you want to keep using the same property name on both SomeClass
and ProtocolA
, you can do除了 Harmish 的出色回应之外,如果您想在
SomeClass
和ProtocolA
上继续使用相同的属性名称,您还可以这样做
protocol ProtocolB {}
protocol ProtocolA {
var _someProperty_protocolA: ProtocolB { get }
}
extension ProtocolA {
var someProperty: ProtocolB {
return _someProperty_protocolA
}
}
class ConformsToB: ProtocolB {}
class SomeClass: ProtocolA {
// the *actual* implementation of someProperty.
var _someProperty: ConformsToB
var someProperty: ConformsToB {
// You can't expose someProperty directly as
// (SomeClass() as ProtocolA).someProperty would
// point to the getter in ProtocolA and loop
return _someProperty
}
// dummy property to satisfy protocol conformance.
var _someProperty_protocolA: ProtocolB {
return someProperty
}
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
}
let foo = SomeClass(someProperty: ConformsToB())
// foo.someProperty is a ConformsToB
// (foo as ProtocolA).someProperty is a ProtocolB
This can be useful when you are conforming to another protocol ProtocolA2
that would originally also have constraint on someProperty
as well, or when you want to hide your hack around swift limitations.当您符合另一个最初也对
someProperty
也有约束的协议ProtocolA2
,这可能很有用,或者当您想隐藏对 swift 限制的 hack 时。
I'm now curious to know why Swift is not doing this for me directly.我现在很想知道为什么 Swift 不直接为我做这件事。
Beginning in Swift 5.1, you can use opaque return types to reference a protocol that references another protocol, so long as you also use associatedtypes to do so.从 Swift 5.1 开始,你可以使用不透明的返回类型来引用一个引用另一个协议的协议,只要你也使用关联类型来这样做。
Not only does it work for readonly "get" properties, but also readwrite properties.它不仅适用于只读“获取”属性,还适用于读写属性。 For example,
例如,
protocol ProtocolA {
associatedtype T: ProtocolB
var someProperty: T { get }
var x: Int { get set }
}
protocol ProtocolB {
var x: Int { get set }
}
struct ConformsToB: ProtocolB {
var x: Int
}
class SomeClass: ProtocolA {
var someProperty: ConformsToB
init(someProperty: ConformsToB) {
self.someProperty = someProperty
}
var x: Int {
get {
someProperty.x
}
set {
someProperty.x = newValue
}
}
}
var protocolA: some ProtocolA = SomeClass(someProperty: ConformsToB(x: 1))
print(protocolA.x) // 1
protocolA.x = 2
print(protocolA.x) // 2
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.