这很有趣。长话短说(好吧也许没那么短)——这是 #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 使一致性非法(为了使第一个示例中的一致性合法),无论是否有回报,它都会这样做。C
D
Self
我们实际上想用扩展来表达的是Self
must 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
.