不能通过使用 Where 子句创建扩展来符合协议

Can not conform to protocol by creating extension with Where Clauses

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'

swift4.1的CHANGELOG我仔细看了,没找到有用的东西

这正常吗,我是不是漏了什么?

这很有趣。长话短说(好吧也许不是 that short)——它是 an intentional side effect of #12174,它允许协议扩展方法 return Self 来满足协议要求非最终 classes,这意味着您现在可以在 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() 的扩展实现上得到一个令人困惑的错误,说:

Method 'f()' in non-final class 'C' must return Self to conform to protocol '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 return 都在起作用。

其实我们想用扩展表达的是Self一定是,或者继承自C,这就逼着我们考虑个案当 subclass 使用一致性时。

在您的示例中,它看起来像这样:

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

extension UILabel: Typographable {}

extension Typographable <b>where Self : UILabel</b> {

    func setTypography(_ typography: Typography) {

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

其中 在 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 所做的是允许隐式 Self 参数通过 witness(一致的实现)thunk 传播。它通过向受限于符合 class 的那个 thunk 添加一个通用占位符来实现这一点。

所以对于这样的一致性:

class C {}

protocol P {
  func foo()
}

extension P {
  func foo() {}
}

extension C : P {}

在 Swift 4.0.3 中,C 的协议见证 table(我有 可能有助于理解它们)包含一个入口具有签名的 thunk:

(C) -> Void

(请注意,在我 link 的漫谈中,我跳过了存在 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 不匹配。所以编译器拒绝一致性(可以说这太严格了,因为 SelfC 的子类型,我们可以在这里应用逆变,但这是当前的行为)。

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

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.