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"
Solution 1:
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.
Solution 2:
There are two very different issues at play here: The dynamic behavior of protocols and the resolution of protocol "default" implementations.
-
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 originalColor
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
-
Now, in your answer, you question why this falls apart a bit when
B
is a subclass ofA
.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 toRedColor
which has a default implementation forcolor
, butB
is also a subclass ofA
which conforms toColor
, which has a different default implementation ofcolor
.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.)
Solution 3:
I managed to get it working by defining color
on Color
and switching the implementation list for B. Not much good if B
must be an A
though.
protocol Color {
var color : String { get }
}
protocol RedColor: Color {
}
extension Color {
var color : String {
get {return "Default color"}
}
}
extension RedColor {
var color : String {
get {return "Red color"}
}
}
protocol PrintColor {
func getColor() -> String
}
extension PrintColor where Self: Color {
func getColor() -> String {
return color
}
}
class A : Color, PrintColor {
}
class B : RedColor, PrintColor {
}
let a = A().getColor() // "Default color"
let b = B().getColor() // "Red color"