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!!
//: Playground - noun: a place where people can play
import UIKit
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"
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()
, thecolor
instance variable (which may be more accurately written asself.color
) doesn't mean what you think it does, because you are thinking class-polymorphically and the protocol is not. So this works:...because you are asking a class to resolve
color
, but this doesn't do what you expect:...because the
getColor
method is defined entirely in a protocol extension. You can fix the problem by redefininggetColor
in B:Now the class's
getColor
is called, and it has a polymorphic idea of whatself
is.I managed to get it working by defining
color
onColor
and switching the implementation list for B. Not much good ifB
must be anA
though.Note: The proposed solution "Defining
color
as part of the originalColor
protocol" is not solving the problem when you have inheritance involved e.g.RedBerry
inherits fromBlueBerry
which conforms to protocolColor
.I came across this problem whilst trying to implement an "optional" method through a protocol. It can be made to work, in structs, in classes that do not inherit, and also in classes that inherit from a base that implements a non-protocol-default method which can be overridden. The only case that doesn't work is a class that inherits from a base that declares conformity but doesn't provide its own "non-default" implementation - in that case the protocol extension's default is "baked-in" to the base class, and cannot be overridden or re-defined.
Simple example:
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:
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):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.)