简体   繁体   English

为什么协议中的 get-only 属性要求不能被符合的属性满足?

[英]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 的出色回应之外,如果您想在SomeClassProtocolA上继续使用相同的属性名称,您还可以这样做

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM