Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Protocol Extensions overriding

I'm experimenting with Swift protocol extensions and I found this quite confusing behaviour. Could you help me how to get the result I want?

See the comments on the last 4 lines of the code. (You can copy paste it to Xcode7 playground if you want). Thank you!!

protocol Color { } extension Color {  var color : String { return "Default color" } }  protocol RedColor: Color { } extension RedColor { var color : String { return "Red color" } }   protocol PrintColor {           func getColor() -> String }  extension PrintColor where Self: Color {          func getColor() -> String {                  return color     } }   class A: Color, PrintColor { } class B: A, RedColor { }   let colorA = A().color // is "Default color" - OK let colorB = B().color // is "Red color" - OK   let a = A().getColor() // is "Default color" - OK let b = B().getColor() // is "Default color" BUT I want it to be "Red color" 
like image 775
VojtaStavik Avatar asked Jul 15 '15 13:07

VojtaStavik


People also ask

Can we override extension method Swift?

It is not possible to override functionality (like properties or methods) in extensions as documented in Apple's Swift Guide. Extensions can add new functionality to a type, but they cannot override existing functionality. The compiler is allowing you to override in the extension for compatibility with Objective-C.

Can we override protocol in Swift?

Here's the problem comes. An extension can't be overridden because the way Swift implement extension is using static dispatch which means its resolved at compile time.

Can you extend a protocol Swift?

In Swift, you can even extend a protocol to provide implementations of its requirements or add additional functionality that conforming types can take advantage of. For more details, see Protocol Extensions. Extensions can add new functionality to a type, but they can't override existing functionality.

How do you override a method in Swift?

In Swift Inheritance, the subclass inherits the methods and properties of the superclass. This allows subclasses to directly access the superclass members. Now, if the same method is defined in both the superclass and the subclass, then the method of the subclass class overrides the method of the superclass.


2 Answers

The short answer is that protocol extensions don't do class polymorphism. This makes a certain sense, because a protocol can be adopted by a struct or enum, and because we wouldn't want the mere adoption of a protocol to introduce dynamic dispatch where it isn't necessary.

Thus, in getColor(), the color instance variable (which may be more accurately written as self.color) doesn't mean what you think it does, because you are thinking class-polymorphically and the protocol is not. So this works:

let colorB = B().color // is "Red color" - OK 

...because you are asking a class to resolve color, but this doesn't do what you expect:

let b = B().getColor() // is "Default color" BUT I want it to be "Red color" 

...because the getColor method is defined entirely in a protocol extension. You can fix the problem by redefining getColor in B:

class B: A, RedColor {     func getColor() -> String {         return self.color     } } 

Now the class's getColor is called, and it has a polymorphic idea of what self is.

like image 80
matt Avatar answered Sep 24 '22 05:09

matt


There are two very different issues at play here: The dynamic behavior of protocols and the resolution of protocol "default" implementations.

  1. On the dynamic front, we can illustrate the problem with a simple example:

    protocol Color { }  extension Color {     var color: String { return "Default color" } }  class BlueBerry: Color {     var color: String { return "Blue color" } }  let berry = BlueBerry() print("\(berry.color)")                 // prints "Blue color", as expected  let colorfulThing: Color = BlueBerry() print("\(colorfulThing.color)")         // prints "Default color"! 

    As you point out in your answer, you can get the dynamic behavior if you define color as part of the original Color protocol (i.e. thereby instructing the compiler to reasonably expect the conforming classes to implement this method and only use the protocol's implementation if none is found):

    protocol Color {     var color: String { get } }  ...  let colorfulThing: Color = BlueBerry() print("\(colorfulThing.color)")         // now prints "Blue color", as expected 
  2. Now, in your answer, you question why this falls apart a bit when B is a subclass of A.

    I think it helps to remember that the method implementations in protocol extensions are "default" implementations, i.e. implementations to be used if the conforming class doesn't implement it, itself. The source of the confusion in your case comes from the fact that B conforms to RedColor which has a default implementation for color, but B is also a subclass of A which conforms to Color, which has a different default implementation of color.

    So, we might quibble about Swift's handling of this situation (personally I'd rather see a warning about this inherently ambiguous situation), but the root of the problem, in my mind, is that there are two different hierarchies (the OOP object hierarchy of subclasses and the POP protocol hierarchy of protocol inheritance) and this results in two competing "default" implementations.

I know this is an old question, so you've probably long since moved on to other things, which is fine. But if you're still struggling regarding the right way to refactor this code, share a little about what this class hierarchy and what this protocol inheritance actually represent and we might be able to offer more concrete counsel. This is one of those cases where abstract examples just further confuse the issue. Let's see what the types/protocols really are. (If you've got working code, http://codereview.stackexchange.com might be the better venue.)

like image 44
Rob Avatar answered Sep 21 '22 05:09

Rob