【问题标题】:Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?为什么协议中的 get-only 属性要求不能被符合的属性满足?
【发布时间】:2017-03-02 17:09:34
【问题描述】:

为什么下面的代码会报错?

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 的一个缺点,还是有什么原因?

【问题讨论】:

  • 感谢您的链接。这很不幸,但很高兴知道!
  • 如果你想要这种行为,在ProtocolA你应该有associatedtype T: ProtocolB然后声明var someProperty: T { get }
  • 在此期间它可以作为一种解决方法,直到它(希望)被修复,但我真的很犹豫添加一个关联类型,因为这会将这些知识冒泡到对象图的其余部分,很快就会失控。
  • 从 Swiftt 5.1 开始,您可以将不透明返回类型与关联类型结合使用,以避免它们在对象图中冒泡。
  • @IliasKarim 你能把它添加为答案吗?

标签: swift swift-protocols


【解决方案1】:

没有真正的理由不应该这样做,只读属性要求可以是协变的,因为从类型为ProtocolB 的属性返回ConformsToB 实例是完全合法的.

Swift 只是目前不支持它。为此,编译器必须在协议见证表和符合要求的实现之间生成a thunk,以便执行必要的类型转换。例如,ConformsToB 实例需要装箱 in an existential container 才能输入为ProtocolB(调用者不可能这样做,因为它可能不知道任何关于被调用的实现)。

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

[...] 在允许函数转换的每种情况下,我们都需要协议见证和方法覆盖

所以它看起来肯定是 Swift 团队希望在该语言的未来版本中实现的东西。

不过,与此同时,作为@BallpointBen says,一种解决方法是使用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 可以返回符合ProtocolBanything - 现在它说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 的详细回答。我已经按照您的建议(计算属性包装器)进行了操作,但我同意,不幸的是不得不添加另一个属性。
  • 只读协议属性的协方差至少存在 1 个潜在问题。这里:forums.swift.org/t/…
  • 由于版权问题,此答案中提供的 youtube 链接已关闭!
【解决方案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 限制时。

我现在很想知道为什么 Swift 不直接为我做这件事。

【讨论】:

    【解决方案3】:

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

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

    
    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      • 2015-11-21
      相关资源
      最近更新 更多