5
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,但我找不到任何有用的东西。

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

4

1 回答 1

4

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

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()

f()非最终类“C”中的方法“ ”必须返回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(),我们也会构造实例,从而破坏协议契约。因此 Swift 4.1 使一致性非法(为了使第一个示例中的一致性合法),无论是否有回报,它都会这样做。CDSelf

我们实际上想用扩展来表达的是Selfmust be或继承自 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
    }
}

正如 Martin 所说,它在 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
    }
}

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

因此,对于这样的一致性:

class C {}

protocol P {
  func foo()
}

extension P {
  func foo() {}
}

extension C : P {}

在 Swift 4.0.3 中,C的协议见证表(我在这里对 PWT 进行了一些讨论,这可能有助于理解它们)包含一个具有签名的 thunk 的条目:

(C) -> Void

(请注意,在我链接到的漫谈中,我跳过了存在 thunk 的细节,只是说 PWT 包含一个用于满足要求的实现的条目。但语义在大多数情况下是相同的)

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

<Self : C>(Self) -> Void

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

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

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

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

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

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

一切都很好,因为两个签名现在都是<Self : C>(Self) -> 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.

于 2018-04-12T22:39:05.357 回答