Why do classes need to be final when adopting a protocol with a property with type Self?
I suspect the problem you've encountered stems from the fact that read-write properties are invariant in Swift. If you consider a more basic example without Self
:
class A {}
class B : A {}
class Foo {
var a = A()
}
class Bar : Foo {
// error: Cannot override mutable property 'a' of type 'A' with covariant type 'B'
override var a : B {
get {
return B() // can return B (as B conforms to A)
}
set {} // can only accept B (but not A, therefore illegal!)
}
}
We cannot override the read-write property of type A
with a property of type B
. This is because although reading is covariant, writing is contravariant. In other words, we can always make the getter return a more specific type from a given property as we override it – as that specific type will always conform/inherit from the base type of the property. However we cannot make the setter more type restrictive, as we're now preventing given types from being set that our original property declaration allowed to be set.
Therefore we reach this state where we cannot make the property any more or any less type specific as we override it, thus making it invariant.
This comes into play with a Self
typed property requirement, as Self
refers to the runtime type of whatever is implementing the protocol, therefore its static type will be the same type as the class declaration. Therefore when you come to subclass a given class with this protocol requirement, the static type of Self
in that subclass is the type of the subclass – thus the property would need to be covariant in order to meet this requirement, which it isn't.
You may then be wondering why the compiler doesn't allow you to satisfy this protocol requirement with a read-only computed property, which is covariant. I suspect this is due to the fact that a read-only computed property can be overridden by a settable computed property, thus creating invariance. Although I'm not entirely sure why the compiler can't pick that error up at the point of overriding, rather than preventing read-only properties entirely.
In any case, you can achieve the same result through using a method instead:
protocol Bar {
func foo() -> Self
}
class Foo : Bar {
required init() {}
func foo() -> Self {
return self.dynamicType.init()
}
}
If you need to implement a property from a protocol with a Self
requirement, then you will indeed have to make the class final
in order to prevent subclassing and therefore violations of the protocol requirement for read-write properties.
This reason that this all works fine with method inputs is due to overloading. When you implement a method with a Self
input type in a subclass, you're not overriding the one in the superclass – you're simply adding another method that can be called. When you come to call the method, Swift will favour the one with the more type specific signature (which you can see in your example).
(Note if you try and use the override
keyword on your method, you'll get a compiler error.)
Also as you've discovered for when Self
refers to a method input, you don't even have to implement a new method in your subclass to take account of the change in Self
's type. This is due to the contravariance of method inputs, which I explain further in this answer. Basically the superclass method already satisfies the subclass requirement, as you can freely replace the type of a given method input with its supertype.
Self, protocol extension and non-final class
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 declareextension 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 signaturestatic func loadFromNib(name: String?) -> UIView
, becauseSelf
in this context isUIView
."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, theNibLoadable
protocol that all subclasses also conform to says that the return type of that method must beSelf
. And in the case of any UIView subclass (for example, let's say "MyView"), the return type of the inherited method will beUIView
and notMyView
. So any subclass would then violate the contract of the protocol. I realize that your protocol extension usesSelf
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 withSelf
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 aSelf
type that is a subclass of the required type ofUIView
.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 returningUIView
matches the implementation UIView is getting which returnsSelf
(which happens to beUIView
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 theUIView
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 returnsMyView
would satisfy the protocol requirement that a method return aUIView
, 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 ofSelf
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 ofUIView
will not match the protocol extension method's return type ofSelf
except in the single case of UIView. This may be a bug, in my opinion, becauseSelf
for any subclass ofUIView
should meet the requiredUIView
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 aUIView
.
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.
Candidate is not '@objc' but protocol requires it
From what I can tell, marking your protocol as @objc means that any classes implementing it also have to be exposed to Objective-C. This can be done either by making Vicki a subclass of NSObject:
class Vicki: NSObject, Speaker {
Or by marking each implemented method as @objc:
class Vicki: Speaker {
@objc func Speak() {
print("Hello, I am Vicki!")
}
@objc func TellJoke() {
print("Q: What did Sushi A say to Sushi B?")
}
}
Update: From Apple's Swift Language Documentation
Optional protocol requirements can only be specified if your protocol is marked with the @objc attribute.
...
Note also that @objc protocols can be adopted only by classes, and not
by structures or enumerations. If you mark your protocol as @objc in
order to specify optional requirements, you will only be able to apply
that protocol to class types.
Swift struct adopting protocol with static read-write property doesn't conform?
At this point I'm persuaded by Nate Cook's example that this is nothing but a bug in the Swift compiler. As he points out, merely adding an empty didSet
observer on the static variable allows the code to compile. The fact that this could make a difference, even though it makes no functional difference, has "bug" written all over it.
Satisfy protocol requirement with property of covariant type
Question: Are there any workarounds for this limitation?
You could use an associatedtype
in P
(conforming to V
) that is used as the type annotation of v
in P
, and use this associatedtype
as a generic typeholder for types conforming to P
.
protocol V {}
protocol P {
associatedtype T: V
var v: T? { get }
}
/* typeholder conformance via protocol inheritance */
protocol Some: V {}
class B<T: Some>: P {
var v: T?
}
/* ... protocol composition */
protocol Another {}
class C<T: Another & V>: P {
var v: T?
}
Extension of a protocol where the associatedType is class?
You could just an add intermediate assignment to a var. For a class/reference type, this would have the same effect as setting a property on the original reference. For a struct type it would make a copy, which wouldn't work, but should be avoided by the constraint on the extension.
func changeModel(_ model: Model, completion: @escaping () -> Void) {
var modelRef = model
Api.shared.doRandomAsyncStuff() { (_) in
modelRef.name = "changed"
completion()
}
}
AnyClass is NSObjectProtocol... sometimes?
You are correct that WKObject
implements the NSObject protocol
as it implements the WKObject protocol
and this protocol inherits from the NSObject protocol
. But that plays no role here.
Certain methods like +isSubclassOfClass:
or +instancesRespondToSelector:
are not declared in the NSObject
protocol, these are just normal class methods of the NSObject class
and thus inherited by all sub-classes of NSObject
but only by sub-classes of NSObject
. Other root classes must implement these themselves if they want to be NSObject
compatible, the NSObject
protocol won't force them to do so.
Now check out this code from a unit test class:
SEL issubclasssel = @selector(isSubclassOfClass:);
Protocol * nsobjp = @protocol(NSObject);
Class c1 = NSClassFromString(@"NSObject");
XCTAssert(c1);
XCTAssert([c1 conformsToProtocol:nsobjp]);
XCTAssert([c1 instancesRespondToSelector:issubclasssel]);
XCTAssert([c1 isSubclassOfClass:[NSObject class]]);
Class c2 = NSClassFromString(@"WKNSURLRequest");
XCTAssert(c2);
XCTAssert([c2 conformsToProtocol:nsobjp]); // Line 1
XCTAssert([c2 instancesRespondToSelector:issubclasssel]); // Line 2
XCTAssert([c2 isSubclassOfClass:[NSObject class]]); // Line 3
This code crashes at Line 2:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)
And if I comment out Line 2, the code still crashes at Line 3 with exactly the same error. Note that this is not Swift code, nor in any way Swift related, this is pure Objective-C code. It's just wrong Objective-C code, as you will see below.
So WKObject
does implement +conformsToProtocol:
(Line 1 does not crash), it has to, as this is a requirement of the NSObject protocol
, but it doesn't implement +instancesRespondToSelector:
or +isSubclassOfClass:
, and it doesn't have to, so this is perfectly okay. It is a root class, it doesn't inherit from NSObject
and there is no protocol that would require it to implement any of these. It was my mistake above to call these methods; calling not-existing methods on objects is "undefined behavior" and this allows the runtime pretty much anything: ignoring the call, just logging an error, throwing an exception, or just crashing right away; and as objc_msgSend()
is a highly optimized function (it has no security checks, that would too expensive for every call), it just crashes.
But apparently Swift sometimes doesn't seem to care. When dealing with Obj-C objects, Swift seems to assume that it can always call one of theses methods that NSObject
and any sub-class of it implement, even though no protocol would promise that. And that's why certain Swift code constructs cause a crash with Objective-C root objects that don't inherit from NSObject
. And as this assumption is simply wrong. Swift must never call any methods on root objects where it doesn't know for sure that these methods are also implemented. Thus I called this a bug in the Swift-Objc-Bridge at the other question.
Update 1:
Giuseppe Lanza asked:
Why then when I have a pure swift class, I get the class from string
and then I test is NSObjectProtocol I get true?
Personally I think this is also a bug in the Swift Runtime. A pure Swift class does not conform to the NSObjectProtocol. Actually it cannot even conform to it, see answer below.
Giuseppe Lanza asked:
Please note that if I create a protocol that inherits from
NSObjectProtocol and then I try to make PureClass conformance to that
protocol the compiler will complain that PureClass is not
NSObjectProtocol compliant
That's because PureClass
would have to implement all required NSObjectProtocol
methods to conform to that protocol; see this answer https://stackoverflow.com/a/24650406/15809
However, it cannot even satisfy that requirement, as one requirement of NSObjectProtocol
is to implement this method
func `self`() -> Self
and that's simply not possible for a pure Swift class, as when you try to do that, the compiler will complain:
error: method cannot be an implementation of an @objc requirement
because its result type cannot be represented in Objective-C
which is correct, a pure Swift class cannot be represented in Obj-C, so it cannot return the required type.
The Swift documentation also says:
Note that @objc protocols can be adopted only by classes that
inherit from Objective-C classes or other @objc classes.
And currently @objc
forces you to inherit from NSObject
, which a pure Swift class does not.
Related Topics
How to Decrease a Value Using Fieldvalue in Firestore (Swift)
Can't Use 'Shape' as Type in Swiftui
iOS 13 Swiftui: App Crashes Upon Launch on Real Device
How to Get All Characters of the Font with Ctfontcopycharacterset() in Swift
Prepare for Segue with Array - Xcode 8.0 Swift 3.0
Xcode Swift 3: Timer and Segue View Controller Error
Why Does Xcode Line-Out Autocomplete Methods for Selector
Why Reloaddata Is Showing Error for My Tableview Inside Viewcontroller
How to Make Uislider Default Thumb to Be Smaller Like the Ones in the iOS Control Center
Swift Access Array with Index Gives Following Error. Any Idea Why
Getch() Equivalent in Swift: Read a Single Character from Stdin Without a Newline
Swift How to Sort Dict Keys by Byte Value and Not Alphabetically
Presenting a Uiviewcontroller from Skscene Shows Black Screen
Iterate a Grid of Views Swiftui
How to Check for Value in Firebase That Is Held Under a Autoid Child