【问题标题】:Can not conform to protocol by creating extension with Where Clauses无法通过使用 Where 子句创建扩展来符合协议
【发布时间】:2018-04-12 09:15:50
【问题描述】:
protocol Typographable {
    func setTypography(_ typography: Typography)
}

extension UILabel: Typographable {}

extension Typographable where Self == UILabel {

    func setTypography(_ typography: Typography) {

        self.font = typography.font
        self.textColor = typography.textColor
        self.textAlignment = typography.textAlignment
        self.numberOfLines = typography.numberOfLines
    }
}

我已经创建了一个协议TypographableUILabel实现了这个协议,实现在extension Typographable where Self == UILabel中。

它在 swift 4.0 中完美运行,但在 swift 4.1 中不再运行,错误消息为Type 'UILabel' does not conform to protocol 'Typographable'

我已经仔细阅读了swift 4.1的CHANGELOG,但是我找不到任何有用的东西。

这正常吗,我错过了什么吗?

【问题讨论】:

  • 为什么不简单地extension UILabel: Typographable { ... }
  • @MartinR 因为我有另一个XXLabel,它有不同的setTypography 实现。如果我使用extension UILabel: Typographable { ... },我必须写类似if self is XXLabel 的东西,这并不优雅。
  • 我目前没有解释(我无法验证 Swift 4.0 中的行为是否不同),但 extension Typographable where Self: UILabel { ... } 会使其编译
  • 我可以确认您的方法适用于 Swift 4 (Xcode 9.2),但不适用于 Swift 4.1。
  • @JIEWANG XXLabel 是否继承自UILabel?在这种情况下,请注意,当您将UILabelTypographable 一致时,动态调度到setTypography(在协议类型值上调用时)将调度到UILabel 扩展,而不是Typographable。所以你的方法也不会在 Swift 4.0.3 中完全奏效。

标签: swift swift4.1


【解决方案1】:

这很有趣。长话短说(好吧,也许不是那么短)——它是#12174an intentional side effect,它允许返回Self的协议扩展方法满足非最终类的协议要求,这意味着你现在可以在 4.1 中这样说:

protocol P {
  init()
  static func f() -> Self
}

extension P {
  static func f() -> Self {
    return self.init()
  }
}

class C : P {
  required init() {}
}

在 Swift 4.0.3 中,您会在 f() 的扩展实现上遇到一个令人困惑的错误:

非最终类“C”中的方法“f()”必须返回Self以符合协议“P

这如何适用于您的示例?好吧,考虑这个有点相似的例子:

class C {}
class D : C {}

protocol P {
  func copy() -> Self
}

extension P where Self == C {
  func copy() -> C {
    return C()
  }
}

extension C : P {}

let d: P = D()
print(d.copy()) // C (assuming we could actually compile it)

如果 Swift 允许协议扩展的 copy() 实现满足要求,即使在 D 实例上调用时,我们也会构造 C 实例,从而破坏协议契约。因此 Swift 4.1 使一致性非法(为了使第一个示例中的一致性合法),并且无论是否有 Self 返回在游戏中都会这样做。

我们实际上想用扩展来表达的是Self 必须是,或继承自 C,这迫使我们考虑子类使用一致性的情况。

在您的示例中,如下所示:

protocol Typographable {
    func setTypography(_ typography: Typography)
}

extension UILabel: Typographable {}

extension Typographable where Self : UILabel {

    func setTypography(_ typography: Typography) {

        self.font = typography.font
        self.textColor = typography.textColor
        self.textAlignment = typography.textAlignment
        self.numberOfLines = typography.numberOfLines
    }
}

as Martin says 在 Swift 4.1 中编译得很好。尽管正如 Martin 所说,这可以以更直接的方式重写:

protocol Typographable {
    func setTypography(_ typography: Typography)
}

extension UILabel : Typographable {

    func setTypography(_ typography: Typography) {

        self.font = typography.font
        self.textColor = typography.textColor
        self.textAlignment = typography.textAlignment
        self.numberOfLines = typography.numberOfLines
    }
}

在技术细节上,#12174 所做的是允许通过见证(符合实现)thunk 传播隐式 Self 参数。它通过将通用占位符添加到受限于符合类的 thunk 来做到这一点。

所以对于这样的一致性:

class C {}

protocol P {
  func foo()
}

extension P {
  func foo() {}
}

extension C : P {}

在 Swift 4.0.3 中,C 的协议见证表(我有 a little ramble on PWTs here 可能有助于理解它们)包含一个具有签名的 thunk 条目:

(C) -> Void

(请注意,在我链接到的漫谈中,我跳过了存在 thunk 的细节,只是说 PWT 包含用于满足要求的实现的条目。语义大部分是,不过一样)

然而在 Swift 4.1 中,thunk 的签名现在看起来像这样:

<Self : C>(Self) -> Void

为什么?因为这允许我们传播Self 的类型信息,允许我们保留要在第一个示例中构造的实例的动态类型(并使其合法)。

现在,对于一个看起来像这样的扩展:

extension P where Self == C {
  func foo() {}
}

扩展实现的签名(C) -&gt; Void 和thunk 的签名&lt;Self : C&gt;(Self) -&gt; Void 不匹配。所以编译器拒绝一致性(可以说这太严格了,因为SelfC 的子类型,我们可以在这里应用逆变,但这是当前的行为)。

但是,如果我们有扩展名:

extension P where Self : C {
  func foo() {}
}

一切又好了,因为两个签名现在都是&lt;Self : C&gt;(Self) -&gt; Void

关于#12174 的一件有趣的事情是,当需求包含相关类型时,它会保留旧的 thunk 签名。所以这行得通:

class C {}

protocol P {
  associatedtype T
  func foo() -> T
}

extension P where Self == C {
  func foo() {} // T is inferred to be Void for C.
}

extension C : P {}

但您可能不应该诉诸这种可怕的解决方法。只需将协议扩展约束更改为where Self : C

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-19
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    相关资源
    最近更新 更多