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
}
}
我创建了一个协议Typographable
,UILabel
实现了此协议,并且实现位于extension Typographable where Self == UILabel
。
它在swift 4.0中完美运行,但在swift 4.1中不再有效,错误信息为Type 'UILabel' does not conform to protocol 'Typographable'
我仔细阅读了swift 4.1的CHANGELOG,但我找不到任何有用的东西。
这是正常的,我错过了什么吗?
答案 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
不匹配。所以编译器拒绝一致性(可以说这太严格了,因为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
。