Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Is it possible to add a protocol extension to a protocol?

Lets say I have two protocols:

protocol TheirPcol {} protocol MyPcol {     func extraFunc() } 

What I want to do is to create a protocol extension for 'TheirPcol' which lets extraFunc() work on anything which conforms to 'TheirPcol'. So something like this:

extension TheirPcol : MyPcol { // Error 'Extension of protocol 'TheirPcol' cannot have an inheritance clause.     func extraFunc() { /* do magic */} }  struct TheirStruct:TheirPcol {} let inst = TheirStruct() inst.extraFunc() 

The kicker in this is that 'TheirPcol', 'TheirStruct' are all handled by an external API which I do not control. So I'm passed the instance 'inst'.

Can this be done? Or am I going to have to do something like this:

struct TheirStruct:TheirPcol {} let inst = TheirStruct() as! MyPcol inst.extraFunc() 
like image 375
drekka Avatar asked Feb 02 '17 03:02

drekka


People also ask

Can we extend protocol?

Protocol extensions are different. You cannot “extend” a protocol because by definition a protocol doesn't have an implementation - so nothing to extend. (You could say that we “extend a protocol WITH some functionality”, but even an extended protocol is not something we can apply a function to.)

Can a protocol inherit from another protocol Swift?

One protocol can inherit from another in a process known as protocol inheritance. Unlike with classes, you can inherit from multiple protocols at the same time before you add your own customizations on top. Now we can make new types conform to that single protocol rather than each of the three individual ones.

What is the difference between an extension and a protocol extension?

Protocols let you describe what methods something should have, but don't provide the code inside. Extensions let you provide the code inside your methods, but only affect one data type – you can't add the method to lots of types at the same time.

How do I add a protocol in Swift?

To create a protocol, use the protocol keyword followed by the name you want and defined by the curly braces. Protocols can be of 2 types: read-only/read-write. Read-only means you can only get the variable, but you cannot set it. Read-write means you can both set and get properties.


1 Answers

It seems there are two use-cases of why you may want to do what you are doing. In the first use-case, Swift will allow you to do what you want, but not very cleanly in the second use-case. I'm guessing you fall into the second category, but I'll go through both.

Extending the functionality of TheirPcol

One reason why you might want to do this is simply to give extra functionality to TheirPcol. Just like the compiler error says, you cannot extend Swift protocols to conform to other protocols. However, you can simply extend TheirPcol.

extension TheirPcol {     func extraFunc() { /* do magic */ } } 

Here, you are giving all objects that conform to TheirPcol the method extraFunc() and giving it a default implementation. This accomplishes the task of extending functionality for the objects conforming to TheirPcol, and if you want it to apply to your own objects as well then you could conform your objects to TheirPcol. In many situations, however, you want to keep MyPcol as your primary protocol and just treat TheirPcol as conforming to MyPcol. Unfortunately, Swift does not currently support protocol extensions declaring conformance to other protocols.

Using TheirPcol objects as if they were MyPcol

In the use case (most likely your use case) where you really do need the separate existence of MyPcol, then as far as I am aware there is no clean way to do what you want yet. Here's a few working but non-ideal solutions:

Wrapper around TheirPcol

One potentially messy approach would be to have a struct or class like the following:

struct TheirPcolWrapper<T: TheirPcol>: MyPcol {     var object: T      func extraFunc() { /* Do magic using object */ } } 

You could theoretically use this struct as an alternative to casting, as in your example, when you need to make an existing object instance conform to MyPcol. Or, if you have functions that accept MyPcol as a generic parameter, you could create equivalent functions that take in TheirPcol, then convert it to TheirPcolWrapper and send it off to the other function taking in MyPcol.

Another thing to note is if you are being passed an object of TheirPcol, then you won't be able to create a TheirPcolWrapper instance without first casting it down to an explicit type. This is due to some generics limitations of Swift. So, an object like this could be an alternative:

struct TheirPcolWrapper: MyPcol {     var object: MyPcol      func extraFunc() { /* Do magic using object */ } } 

This would mean you could create a TheirPcolWrapper instance without knowing the explicit type of the TheirPcol you are given.

For a large project, though, both of these could get messy really fast.

Extending individual objects using a child protocol

Yet another non-ideal solution is to extend each object that you know conforms to TheirPcol and that you know you wish to support. For example, suppose you know that ObjectA and ObjectB conform to TheirPcol. You could create a child protocol of MyPcol and then explicitly declare conformance for both objects, as below:

protocol BridgedToMyPcol: TheirPcol, MyPcol {}  extension BridgedToMyPcol {     func extraFunc() {         // Do magic here, given that the object is guaranteed to conform to TheirPcol     } }  extension ObjectA: BridgedToMyPcol {} extension ObjectB: BridgedToMyPcol {} 

Unfortunately, this approach breaks down if there are a large number of objects that you wish to support, or if you cannot know ahead of time what the objects will be. It also becomes a problem when you don't know the explicit type of a TheirPcol you are given, although you can use type(of:) to get a metatype.

A note about Swift 4

You should check out Conditional conformances, a proposal accepted for inclusion in Swift 4. Specifically, this proposal outlines the ability to have the following extension:

extension Array: Equatable where Element: Equatable {     static func ==(lhs: Array<Element>, rhs: Array<Element>) -> Bool { ... } } 

While this is not quite what you are asking, at the bottom you'll find "Alternatives considered", which has a sub-section called "Extending protocols to conform to protocols", which is much more what you're trying to do. It provides the following example:

extension Collection: Equatable where Iterator.Element: Equatable {     static func ==(lhs: Self, rhs: Self) -> Bool {         // ...     } } 

Then states the following:

This protocol extension would make any Collection of Equatable elements Equatable, which is a powerful feature that could be put to good use. Introducing conditional conformances for protocol extensions would exacerbate the problem of overlapping conformances, because it would be unreasonable to say that the existence of the above protocol extension means that no type that conforms to Collection could declare its own conformance to Equatable, conditional or otherwise.

While I realize you're not asking for the ability to have conditional conformances, this is the closest thing I could find regarding discussion of protocols being extended to conform to other protocols.

like image 135
Matthew Seaman Avatar answered Sep 16 '22 14:09

Matthew Seaman