自我、协议扩展和非最终类 [英] Self, protocol extension and non-final class

查看:75
本文介绍了自我、协议扩展和非最终类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试为 UIView 编写一个静态方法,该方法从笔尖实例化该类的视图.方法应该是通用的并且适用于每个 UIView 子类.我还想保存类型信息——例如,在这段代码中

I tried write a static method for UIView which instantiates view of that class from the nib. Method should be generic and work on every UIView subclasses. Also I want to save the type information – so, for example, in this code

let myView = MyView.loadFromNib()

编译器推断 myViewMyView 类.经过几次试验后,我决定使用协议扩展,否则我将无法访问方法体内的 Self.

compiler infers that myView has MyView class. After few trials I decided to use protocol extensions, because otherwise I wouldn't have access to Self inside method body.

看起来应该可以:

protocol NibLoadable {
    static func loadFromNib(name: String?) -> Self
}

extension NibLoadable where Self: UIView {
    static func loadFromNib(name: String? = nil) -> Self {
        let nibName = name ?? "\(self)"
        let nib = UINib(nibName: nibName, bundle: nil)
        return nib.instantiateWithOwner(nil, options: nil)[0] as! Self
    }
}

extension UIView: NibLoadable {}

但事实并非如此.我收到编译错误

But it doesn't. I get compilation error

Method 'loadFromNib' in non-final class 'UIView' must return `Self` to conform to protocol 'NibLoadable'

然后发生了两件奇怪的事情.首先,如果我将协议声明更改为

And two strange things happen. First, if I change protocol declaration to

protocol NibLoadable {
    static func loadFromNib(name: String?) -> UIView
}

一切都很好,包括类型推断.其次,我可以更进一步,完全删除 where 子句:

Everything works just great, including type inference. And second, I can go further and remove where clause altogether:

extension NibLoadable {
    ...
}

它一直在工作!

那么谁能解释一下为什么我的第一个变体失败了,为什么第二个和第三个可以正常工作以及它与最终课程有什么关系?

So could anyone please explain me why my first variant fails, why second and third work fine and how it's related to final classes?

推荐答案

以下是我对您所看到的内容的理解:

Here's my understanding of what you are seeing:

  1. 你得到编译错误 Method 'loadFromNib' in non-final class 'UIView' must return 'Self' to符合协议 'NibLoadable' 在你声明 扩展 UIView:NibLoadable {}.让我们看看这个语句对编译器意味着什么.它说UIView(以及它的所有子类,因为它是一个非最终类)正在采用 NibLoadable 协议.这意味着,对于 UIView,会有带有签名的方法 static func loadFromNib(name: String?) -> UIView,因为 Self 在这个上下文中 UIView."

  1. You get the compilation error Method 'loadFromNib' in non-final class 'UIView' must return 'Self' to conform to protocol 'NibLoadable' at the point where you declare extension UIView: NibLoadable {}. Let's look at what this statement means to the compiler. It's saying "UIView (and all of its subclasses since it is a non-final class) are adopting the NibLoadable protocol. It means, for UIView, that there will be method with the signature static func loadFromNib(name: String?) -> UIView, because Self in this context is UIView."

但这对于 UIView 的子类意味着什么呢?它们继承它们的一致性并且可能继承 UIView 本身的方法实现.所以 UIView 的任何子类 都可以拥有带有签名的方法 static func loadFromNib(name: String? = nil) ->UIView.但是,所有子类也遵循的 NibLoadable 协议规定该方法的返回类型必须是 Self.对于任何 UIView 子类(例如,假设MyView"),继承方法的返回类型将是 UIView 而不是 MyView.因此,任何子类都会违反协议的约定.我意识到您的协议扩展使用 Self 并且不会产生该问题,但从技术上讲,您仍然可以直接在 UIView 扩展中实现该方法,而且 Swift 编译器似乎不会出于这个原因,完全允许它.更好的实现可能会发现 Swift 编译器验证存在提供实现的协议扩展,并且没有冲突的继承实现,但此时似乎不存在.所以为了安全起见,我的猜测是编译器会阻止任何具有 Self 返回类型的方法的协议被非最终类采用.因此,您看到的错误.

But what does this mean for subclasses of UIView? They inherit their conformance and might inherit the implementation of the method from UIView itself. So any subclass of UIView could have the method with the signature static func loadFromNib(name: String? = nil) -> UIView. However, the NibLoadable protocol that all subclasses also conform to says that the return type of that method must be Self. And in the case of any UIView subclass (for example, let's say "MyView"), the return type of the inherited method will be UIView and not MyView. So any subclass would then violate the contract of the protocol. I realize that your protocol extension uses Self and wouldn't create that issue, but technically, you could still also implement the method directly in a UIView extension, and it seems like the Swift compiler just won't allow it at all for that reason. A better implementation might find the Swift compiler verifying that a protocol extension exists which provides the implementation and there is no conflicting inherited implementation, but this appears to just not exist at this time. So for safety, my guess is that the compiler prevents ANY protocols that have methods with Self return types from being adopted by a non-final class. Thus the error you see.

但是,使 UIView 成为最终类会使不符合方法的整个继承可能性和问题消失,从而修复错误.

However, making UIView a final class makes that whole inheritance of a non-conforming method possibility and issue go away, which fixes the error.

之所以将协议中的返回类型更改为 UIView 可以解决所有问题,是因为现在没有将Self"作为返回类型,从而减轻了编译器对具有不符合标准的返回类型的方法的继承版本的担忧.例如,如果 UIView 要实现方法 static func loadFromNib(name: String?) ->UIView,并且子类继承了那个方法,协议契约对于那些子类仍然有效,所以没有问题!

The reason why changing the return type in the protocol to UIView fixes everything is because not having 'Self' as the return type now relieves the compiler's concern about inherited versions of the method having a non-conforming return type. E.g., if UIView were to implement the method static func loadFromNib(name: String?) -> UIView, and subclasses inherited that method, the protocol contract would still hold for those subclasses, so there is no problem!

另一方面,类型推断是有效的,因为 UIView 的子类从协议扩展中获取它们的方法实现(因为该方法不是直接在 UIView 中实现的).该实现返回类型 Self,它告诉编译器返回的值与调用方法的类型具有相同的类型,并且满足协议,因为 UIView 的任何子类都会有一个 Self 类型,它是 UIView 所需类型的子类.

On the other hand, the type inference works, because the subclasses of UIView are getting their method implementation from the protocol extension (since the method is not implemented directly in UIView). That implementation returns the type Self, which tells the compiler that the returned value has the same type as the type the method was called on, and the protocol is satisfied, because any subclass of UIView will have a Self type that is a subclass of the required type of UIView.

删除 where 子句仅在这种特定情况下有效,因为您将协议方法更改为返回 UIView,并且协议扩展定义了一个匹配的方法实现,该实现返回 Self,然后仅返回 UIView正在您的示例代码中获得该扩展名.因此,返回 UIView 的方法的协议要求与返回 Self 的实现 UIView 匹配(在这种情况下恰好是 UIView).但是,如果您尝试使 UIView 以外的任何类型获得协议扩展方法,例如

Removing the where clause works only in this specific case because you changed the protocol method to return UIView, and the protocol extension defines a matching method implementation that returns Self, and then only UIView is getting that extension in your sample code. So the protocol requirement of the method returning UIView matches the implementation UIView is getting which returns Self (which happens to be UIView in this case). But, should you try to make any type other than UIView get the protocol extension method, e.g.

class SomeClass : NibLoadable {}

甚至

class MyView:UIView, NibLoadable {}

编译器不允许这样做,因为协议扩展方法中的 Self 返回类型与协议中所需的 UIView 不匹配.我觉得在MyView"或其他 UIView 子类的情况下,编译器错误可能是一个错误,因为返回 MyView 的方法将满足方法返回 的协议要求UIView,如果 MyView 确实继承自 UIView.

the compiler wouldn't allow it, because the Self return type in the protocol extension method would not match the UIView required in the protocol. I feel like in the case of "MyView" or other UIView subclasses though, the compiler error might be a bug, since a method that returns MyView would satisfy the protocol requirement that a method return a UIView, if MyView did inherit from UIView.

总结一些要点:

  • 看起来协议扩展在您注意到的编译器错误中没有任何作用.只是这也会产生错误:

  • It doesn't look like the protocol extension has any role in the compiler error you noted. Just this will also create the error:

protocol NibLoadable {
    static func loadFromNib(name: String?) -> Self
}

extension UIView: NibLoadable {}

因此看起来编译器不允许非 final 类通过使用返回类型为 Self、句点的方法的默认实现来采用协议.

So it looks like the compiler doesn't allow non-final classes to adopt protocols by using default implementations of methods that have a return type of Self, period.

如果您将协议方法签名更改为返回 UIView 而不是 Self,那么特定的编译器警告就会消失,因为子类不再有可能继承超类返回类型并破坏协议.然后您可以使用您的协议扩展将协议一致性添加到 UIView.然而,如果你尝试为除 UIView 之外的任何类型采用该协议,你将得到一个 不同 错误,因为 UIView 的协议返回类型将不匹配协议扩展方法的 Self 返回类型,除了 UIView 的单一情况.在我看来,这可能是一个错误,因为 UIView 的任何子类的 Self 都应该满足所需的 UIView 返回类型协定.

If you change the protocol method signature to return UIView instead of Self that particular compiler warning goes away because there is no longer the possibility of subclasses inheriting a superclass return type and breaking the protocol. And you can then add conformance to the protocol to UIView with your protocol extension. However, you will get a different error if you try to adopt the protocol for any type other than UIView, because the protocol return type of UIView will not match the protocol extension method's return type of Self except in the single case of UIView. This may be a bug, in my opinion, because Self for any subclass of UIView should meet the required UIView return type contract.

但奇怪的是,如果你只在 UIView 中采用协议,UIView 的子类将继承他们对协议的一致性(避免触发上述两个编译器错误中的任何一个)和只要 UIView 没有显式地实现协议方法本身,就可以从协议扩展中获取它们的通用实现.因此子类将获得适当的Self 的类型推断,并满足让该方法返回UIView 的协议约定.

But strangely enough, if you adopt the protocol in UIView only, subclasses of UIView will inherit their conformance to the protocol (avoiding the triggering of any of the two above compiler errors) and get their generic implementations from the protocol extension as long as UIView doesn't explicitly implement the protocol method itself. So the subclasses will get the type inference of appropriate Self, and meet the protocol contract for having that method return a UIView.

我很确定这其中存在一个或多个错误,但 Swift 团队中的某个人必须确认这一点.

I'm pretty sure there are one or more bugs mixed up in all this, but someone on the Swift team would have to confirm that to be sure.

更新

Swift 团队在此 Twitter 线程中的一些说明:

Some clarification from the Swift team in this Twitter thread:

https://twitter.com/_danielhall/status/737782965116141568

正如怀疑的那样,协议匹配不考虑子类型,只考虑精确的类型匹配,这是一个编译器限制(虽然显然不被认为是一个彻头彻尾的错误).这就是为什么当协议方法定义返回类型为 UIViewextension UIView:NibLoadable {} 会起作用,但 extension MyView:NibLoadable {}将不会.

As suspected, it's a compiler limitation (though apparently not considered an outright bug) that protocol matching does not consider subtypes, only exact type matches. Which is why extension UIView:NibLoadable {} will work when the protocol method defines a return type of UIView, but extension MyView:NibLoadable {} will not.

这篇关于自我、协议扩展和非最终类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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