不能通过使用 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
}
}
我已经创建了一个协议 Typographable
,UILabel
实现了这个协议,实现在 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
不匹配。所以编译器拒绝一致性(可以说这太严格了,因为 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
.
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'
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 returnSelf
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
}
}
其中
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(我有
(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
不匹配。所以编译器拒绝一致性(可以说这太严格了,因为 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
.