通过使用Where子句创建扩展名不能符合协议 [英] Can not conform to protocol by creating extension with Where Clauses

查看:85
本文介绍了通过使用Where子句创建扩展名不能符合协议的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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的 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不匹配.因此,编译器拒绝了一致性(可以说这太严格了,因为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.

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 return Self 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆