为什么协议中的get-only属性要求必须由符合的属性满足?

时间:2017-03-02 17:09:35

标签: swift swift-protocols

为什么以下代码会产生错误?

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有道理。但是,在我的示例中,属性是get-only。为什么不能这样做?它是Swift的缺点,还是有一些合理的原因?

3 个答案:

答案 0 :(得分:32)

没有理由说明为什么这是不可能的,只读属性要求可以是协变的,因为从类型为{{1}的属性返回ConformsToB实例完全合法。

斯威夫特目前不支持它。为此,编译器必须在协议见证表和符合实现之间生成a thunk,以便执行必要的类型转换。例如,ProtocolB实例需要装箱 in an existential container才能输入为ConformsToB(并且调用者无法执行此操作,因为它可能对所调用的实现一无所知。

但同样,编译器没有理由不能这样做。有多个错误报告,this one特定于只读属性要求,this general one,其中Swift团队成员Slava Pestov说:

  

[...]我们希望在允许功能转换的每种情况下都使用协议见证和方法覆盖

所以它看起来确实是Swift团队希望在未来版本的语言中实现的。

与此同时,作为@BallpointBen says,一种解决方法是使用ProtocolB

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 } } 不再可用作类型(因为它有ProtocolA个要求)。它也改变了协议所说的内容。最初它说associatedtype可以返回符合someProperty任何 - 现在它说ProtocolB的实现只处理一个特定的符合someProperty的具体类型。

另一种解决方法是定义虚拟属性以满足协议要求:

ProtocolB

这里我们基本上是编写编译器的 - 但它也不是特别好,因为它为API添加了一个不必要的属性。

答案 1 :(得分:1)

从 Swift 5.1 开始,您可以使用不透明返回类型来引用引用另一个协议的协议,只要您也使用关联类型来这样做。

它不仅适用于只读“获取”属性,还适用于读写属性。例如,


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

答案 2 :(得分:0)

除了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

当您遵循最初也对ProtocolA2也有约束的另一协议someProperty时,或者当您想绕开快速限制隐藏自己的hack时,这很有用。

我现在很好奇为什么Swift不会直接为我这样做。