Why Does Classa Adopting Protocolb Not Satisfy the Protocolb Requirement

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:

  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."

    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.

  2. 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.

  3. 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.

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



Leave a reply



Submit