通过使用Where子句创建扩展名,无法符合协议

时间:2018-04-12 09:15:51

标签: swift swift4.1

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

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

1 个答案:

答案 0 :(得分:4)

这非常有趣。长话短说(好吧可能不是 短) - 它是an intentional side effec#12174 t,它允许返回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()实现满足要求,即使在C实例上调用,我们也会构造D个实例,从而违反协议契约。因此,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中编译得很好。虽然马丁也说过,但这可以用更简单的方式重写:

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所做的是允许通过见证(符合实现)thunks传播隐式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) -> 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