通过使用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'
我已经仔细阅读了swift的 CHANGELOG 4.1,但找不到任何有用的东西.
这很正常吗,我错过了什么吗?
这很有趣.长话短说(好吧,也许不是这样)–它是故意的一面#12174 的效果,它允许返回的协议扩展方法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()
的协议扩展实现满足要求,那么即使在D
实例上调用它,我们也会构造C
实例,这会破坏协议约定.因此,Swift 4.1将合规性规定为非法(为了使第一个示例中的合规性合法),并且无论是否有Self
退货都将执行此操作.
我们实际上想用扩展名表达的是Self
必须是或继承自 C
,这迫使我们考虑子类正在使用一致性时的情况./p>
在您的示例中,看起来像这样:
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
}
}
其中,如马丁所说,在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
参数重击.它通过将通用占位符添加到受限于符合类的thunk中来实现此目的.
因此对于这样的一致性:
class C {}
protocol P {
func foo()
}
extension P {
func foo() {}
}
extension C : P {}
在Swift 4.0.3中,C
的协议见证表(我在这里在PWT上有一些小麻烦可能有助于理解它们)包含具有签名的thunk条目:
(C) -> Void
(请注意,在我链接到的漫游中,我跳过了重击的细节,只是说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
}
}
I have create a protocol Typographable
, the UILabel
implement this protocol, and the implementation is in the extension Typographable where Self == UILabel
.
it works perfectly in swift 4.0, but it not works any more in swift 4.1, the error message is Type 'UILabel' does not conform to protocol 'Typographable'
I have read carefully the CHANGELOG of swift 4.1, but I can't find anything useful.
Is this normal, did I miss something ?
This is pretty interesting. Long story short (okay maybe not that short) – it's an intentional side effect of #12174, which allows for protocol extension methods that return Self
to satisfy protocol requirements for non-final classes, meaning that you can now say this in 4.1:
protocol P { init() static func f() -> Self } extension P { static func f() -> Self { return self.init() } } class C : P { required init() {} }
In Swift 4.0.3, you would get a confusing error on the extension implementation of f()
saying:
Method '
f()
' in non-final class 'C' must returnSelf
to conform to protocol 'P
'
How does this apply to your example? Well, consider this somewhat similar example:
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)
If Swift allowed the protocol extension's implementation of copy()
to satisfy the requirement, we'd construct C
instances even when called on a D
instance, breaking the protocol contract. Therefore Swift 4.1 makes the conformance illegal (in order to make the conformance in the first example legal), and it does this whether or not there are Self
returns in play.
What we actually want to express with the extension is that Self
must be, or inherit from C
, which forces us to consider the case when a subclass is using the conformance.
In your example, that would look like this:
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
}
}
which, as Martin says, compiles just fine in Swift 4.1. Although as Martin also says, this can be re-written in a much more straightforward manner:
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
}
}
In slightly more technical detail, what #12174 does is allow the propagation of the implicit Self
parameter through witness (conforming implementation) thunks. It does this by adding a generic placeholder to that thunk constrained to the conforming class.
So for a conformance like this:
class C {}
protocol P {
func foo()
}
extension P {
func foo() {}
}
extension C : P {}
In Swift 4.0.3, C
's protocol witness table (I have a little ramble on PWTs here that might be useful in understanding them) contains an entry to a thunk that has the signature:
(C) -> Void
(note that in the ramble I link to, I skip over the detail of there being thunks and just say the PWT contains an entry to the implementation used to satisfy the requirement. The semantics are, for the most part, the same though)
However in Swift 4.1, the thunk's signature now looks like this:
<Self : C>(Self) -> Void
Why? Because this allows us to propagate type information for Self
, allowing us to preserve the dynamic type of the instance to construct in the first example (and so make it legal).
Now, for an extension that looks like this:
extension P where Self == C {
func foo() {}
}
there's a mismatch with the signature of the extension implementation, (C) -> Void
, and the signature of the thunk, <Self : C>(Self) -> Void
. So the compiler rejects the conformance (arguably this is too stringent as Self
is a subtype of C
and we could apply contravariance here, but it's the current behaviour).
If however, we have the extension:
extension P where Self : C {
func foo() {}
}
everything's fine again, as both signatures are now <Self : C>(Self) -> Void
.
One interesting thing to note about #12174 though is that is preserves the old thunk signatures when the requirements contain associated types. So this works:
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 {}
But you probably shouldn't resort to such horrific workarounds. Just change the protocol extension constraint to where Self : C
.
这篇关于通过使用Where子句创建扩展名不能符合协议的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!