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

sol*_*ell 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
    }
}
Run Code Online (Sandbox Code Playgroud)

这个类似问题的答案是有道理的.但是,在我的示例中,属性是get-only.为什么不能这样做?它是Swift的缺点,还是有一些合理的理由?

Ham*_*ish 40

没有真正的理由说明为什么这是不可能的,只读属性要求可以是协变的,因为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
    }
}
Run Code Online (Sandbox Code Playgroud)

但这是非常不令人满意的,因为这意味着它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
    }
}
Run Code Online (Sandbox Code Playgroud)

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