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()
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.
Looks like this should work:
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
}
Everything works just great, including type inference. And second, I can go further and remove where
clause altogether:
extension NibLoadable {
...
}
And it keeps working!
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:
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
."
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.
However, making UIView a final class makes that whole inheritance of a non-conforming method possibility and issue go away, which fixes the error.
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!
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
.
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 {}
or even
class MyView:UIView, NibLoadable {}
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.
To summarize some key points:
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 {}
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.
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.
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
.
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.
UPDATE
Some clarification from the Swift team in this Twitter thread:
https://twitter.com/_danielhall/status/737782965116141568
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With