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

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
    }
}


这个类似问题的答案很有意义。但是,在我的示例中,该属性为get-only。为什么不行呢?是Swift的缺点,还是出于某些原因?

评论

感谢您的链接。真不幸,但是很高兴知道!

如果需要这种行为,在ProtocolA中,您应该具有关联类型T:ProtocolB,然后声明var someProperty:T {get}

在(希望)修复之前,这可以作为一种变通方法,但是我真的很犹豫添加一个关联类型,因为那样会使知识扩散到对象图的其余部分,从而很快失去控制。 br />

#1 楼

没有真正的理由为什么不能做到这一点,只读属性要求可以是协变的,因为从类型为ConformsToB的属性返回ProtocolB实例是完全合法的。

Swift目前还不行。支持它。为此,编译器将必须在协议见证表和符合的实现之间生成一个thunk,以执行必要的类型转换。例如,一个ConformsToB实例需要装在一个存在的容器中才能键入为ProtocolB(而且调用者无法执行此操作,因为它可能不知道所调用的实现)。

但是同样,没有理由为什么编译器不应该这样做。有很多关于此的错误报告,这个错误报告专门针对只读属性要求,而对于这个一般的报告,Swift团队的成员Slava Pestov说:


[...]我们希望在每次允许进行函数转换的情况下都使用协议见证人和方法重写


所以,这肯定看起来像Swift团队想要在

同时,正如@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
    }
}


但这是非常不令人满意的,因为这意味着ProtocolA不再可用作类型(因为它具有associatedtype的要求)。它还会更改协议的内容。最初它说someProperty可以返回符合ProtocolB的任何内容–现在它说someProperty的实现只处理一种符合ProtocolB的具体类型。

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

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
    }
}


在这里,我们实质上是在为编译器编写thunk-但它也不是特别好,因为它为API添加了不必要的属性。

评论


感谢您提供详细答案@Hamish。我已经在按照您的建议进行操作了(计算属性包装器),但是我同意,不幸的是必须添加另一个属性。

–solidcell
17 Mar 10 '17 at 12:34

只读协议属性的协方差至少存在1个潜在问题。此处:forums.swift.org/t/…

–安东·别洛索夫(Anton Belousov)
6月23日22:34



#2 楼

除了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不为此而做我直接。