Swift protocol with where Self clause
The ability to put superclass constraints on protocols declarations (that is, being able to define protocol P where Self : C
where C
is the type of a class) was a premature consequence of
SE-0156, and the syntax should have been rejected in Swift 4.x until the feature was implemented. Attempting to use this feature in Swift 4.x can cause miscompilation and crashes, so I would avoid using it until Swift 5.
In Swift 5 (Xcode 10.2) the feature has now been implemented. From the release notes:
Protocols can now constrain their conforming types to those that
subclass a given class. Two equivalent forms are supported:protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ }
Swift 4.2 accepted the second form, but it wasn’t fully implemented
and could sometimes crash at compile time or runtime. (SR-5581)
(38077232)
This syntax places a superclass constraint on MyView
which restricts conforming types to those inheriting from (or being) UIView
. In addition, the usage of MyView
is semantically equivalent to a class existential (e.g UIView & MyView
) in that you can access both members of the class and requirements of the protocol on the value.
For example, expanding upon the release notes' example:
protocol MyView : UIView {
var foo: Int { get }
}
class C : MyView {} // error: 'P' requires that 'C' inherit from 'UIView'
class CustomView : UIView, MyView {
var foo: Int = 0
}
// ...
let myView: MyView = CustomView(frame: .zero)
// We can access both `UIView` members on a `MyView` value
print(myView.backgroundColor as Any)
// ... and `MyView` members as usual.
print(myView.foo)
Swift Protocol inheritance and protocol conformance issue
You cannot implement a read-write property requirement of type BasePresenterProtocol?
with a property of type DashboardPresenterProtocol?
.
Consider what would happen if this were possible, and you upcast an instance of DashboardPresenter
to DashboardViewProtocol
. You would be able to assign anything that conforms to BasePresenterProtocol
to a property of type DashboardPresenterProtocol?
– which would be illegal.
For this reason, a read-write property requirement has to be invariant (although it's worth noting that a readable-only property requirement should be able to be covariant – but this currently isn't supported).
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.
Benefit of class inheritance, when you could use protocol extensions?
The only real benefit I found is that you can create superclass objects, which are no subclass and so you can ignore any implementation details of the subclass.
If A is the superclass of B. Then you can create A and do not have to care about anything from B.
With protocols you always have to use the adopting struct/class.
In some cases it makes sense to create a UIResponder and not a UIViewController, because you do not want that functionality and it is safer and easier to just use a class with less features.
With protocols and their extensions only, you have to choose one implementation, so to not write the same code twice there has to be multiple protocols, one for each hierarchy level of the corresponding class hierarchy.
If P2 adopts P1 and P1E is the extension to P1 and P2E is the extension of P2, then you have to create a struct/class that adopts P1 only to get a less capable version of a struct/class that adopts P2.
Swift -- Require classes implementing protocol to be subclasses of a certain class
I think you are after a subclass of NSView
. Try this:
protocol TransmogrifiableView {
func transmogrify()
}
class MyNSView: NSView, TransmogrifiableView {
// do stuff.
}
And later in the code accept objects of type MyNSView
.
Edit
You maybe want an Extension
, see this
extension NSView: TransmogrifiableView {
// implementation of protocol requirements goes here
}
- Note that you will not be able to get an NSView without this extra method.
- You can separately extend subclasses of NSView to override this new method.
Yet another option is to make a class which holds a pointer to an NSView, and implements additional methods. This will also force you to proxy all methods from NSView that you want to use.
class NSViewWrapper: TransmogrifiableView {
var view : NSView!
// init with the view required.
// implementation of protocol requirements goes here.
.....
// proxy all methods from NSView.
func getSuperView(){
return self.view.superView
}
}
This is quite long and not nice, but will work. I would recommend you to use this only if you really cannot work with extensions (because you need NSViews without the extra method).
Related Topics
How to Catch a Nsinternalinconsistencyexception in Swift
Swift Error: Failed to Get Module 'My_App' from Ast Context
How to Byte Reverse Nsdata Output in Swift The Littleendian Way
Custom Vibration in iOS 8 - Swift
Apple Watch and iPhone Are Not Connected When The App in Phone Goes to Background
"The Requested Snapshot Version Is Too Old." Error in Firestore
Ckasset in Server Record Contains No Fileurl, Cannot Even Check for Nil
Combine Sink: Ignore Receivevalue, Only Completion Is Needed
Carthage Update Error: "Github API Request Failed: Bad Credentials"
iOS App Extension - Action - Custom Data
Convenience Failable Initializers Assign Self
Preventing Nsurlsession Default Http Headers
Swiftui - Button - How to Pass a Function (With Parameters) Request to Parent from Child
How to Get Textfields from Static Cells in UItableviewcontroller? Swift