1

I am learning all about swift, OOP, and POP. I have been mixing them together to make an abstract base class when I came across some unexpected behavior. It is best expressed in code, I will show it working as expected, and then as unexpected (to me at least). The code is long, but simple. Here it is working properly:

protocol GodsWill           {          func conforms() }
extension GodsWill          {          func conforms() { print("Everything conforms to God's Will") } }
class TheUniverse: GodsWill {          func conforms() { print("The Universe conforms to God's Will") } }
class Life: TheUniverse     { override func conforms() { print("Life conforms to God's Will") } }
class Humans: Life          { override func conforms() { print("Though created by God, Humans think they know better") } }

let universe = TheUniverse()
let life = Life()
let humans = Humans()

universe.conforms()
life.conforms()
humans.conforms()
print("-------------------------")
let array:[GodsWill] = [universe,life,humans]
for item in array { item.conforms() }

And here is the output:

The Universe conforms to God's Will
Life conforms to God's Will
Though created by God, Humans sometimes think they know better
-------------------------
The Universe conforms to God's Will
Life conforms to God's Will
Though created by God, Humans sometimes think they know better

This is exactly as I would suspect. But I ran into this problem in my app when the first class didn't have a custom implementation of conforms(), like this:

protocol GodsWill           {          func conforms() }
extension GodsWill          {          func conforms() { print("Everything conforms to God's Will") } }
class TheUniverse: GodsWill {  }
class Life: TheUniverse     {          func conforms() { print("Life conforms to God's Will") } }
class Humans: Life          { override func conforms() { print("Though created by God, Humans sometimes think they know better") } }

let universe = TheUniverse()
let life = Life()
let humans = Humans()

universe.conforms()
life.conforms()
humans.conforms()
print("-------------------------")
let array:[GodsWill] = [universe,life,humans]
for item in array { item.conforms() }

Notice here that TheUniverse does not have a custom implementation of conforms(). Here is the output:

Everything conforms to God's Will
Life conforms to God's Will
Though created by God, Humans sometimes think they know better
-------------------------
Everything conforms to God's Will
Everything conforms to God's Will
Everything conforms to God's Will

The first three print() lines are exactly what I expect and want, but the last three really baffle me. Since conforms() is a protocol requirement, they should be identical to the top three lines. But I am getting behavior as if conforms() was implemented in the protocol extension, but not listed as a protocol requirement. There is nothing about this in The Swift Programming Language reference manual. And this WWDC video at exactly 30:40 in proves my point.

So, have I done something wrong, misunderstood the functionality, or have I found a bug in swift 3?

mogelbuster
  • 1,066
  • 9
  • 19

1 Answers1

1

Upon further investigation, I don't think this is quite related to the WWDC video.

The Universe doesn't define the conforms method, so calling conforms on the universe will print "Everything...".

Life defines a conforms method. However, since it inherits from the universe, which already has an implementation of conforms because of the extension, the conforms method isn't really required by the protocol. Therefore, the protocol kind of ignores the conforms method in the Life class. In other words, the conforms method in Life is shadowing the conforms method in protocol extension, as we can see in the output of:

let a: TheUniverse = Life()
a.conforms()
Life().conforms()
// output:
// Everything conforms to God's Will
// Life conforms to God's Will

Humans have another conforms method. This time, it is defined with override. However, this override does not override the conforms method in the protocol extension. Instead, it overrides the conforms method in Life, which is a direct superclass of Humans. This can be proved by the output of the below code:

let a: Life = Humans()
a.conforms()
// output:
// Though created by God, Humans sometimes think they know better

So after all, the method in the protocol extension is not overridden. So when you create some objects and put it into [GodsWill], the shadowing has no effect, thus the protocol extension method is called.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • So what would be the solution for this problem? I have multiple classes which will conform to GodsWill, all of which I want to have the same default implementation. I guess if I could include a stub in the base class that simply calls the extension implementation, it would complete the circuit and function properly. I tried this: `func conforms() { super.conforms() }` in TheUniverse but this is a compile time error. I tried `func conforms() { (self as GodsWill).conforms() }` in TheUniverse, but this is an infinite loop. How would I call conforms() on GodsWill from TheUniverse? – mogelbuster Oct 23 '16 at 12:21
  • 1
    @mogelbuster Oh yes I forgot to mention that! Just don't make anything inherit from `TheUniverse`. Why can't `Life` directly conform to `GodsWill`? – Sweeper Oct 23 '16 at 12:25
  • `Life` needs to inherit from `TheUniverse`, and `Life` needs to conform to `GodsWill`. That would work, except `TheUniverse` also needs to conform to `GodsWill`. So `Life` can't directly conform to `GodsWill`. I could simply put the default implementation of `conforms()` inside `TheUniverse`, but lets say reality is larger than we thought, and I have two more base classes `EvilParallelUniverse` and `HeavenlyParallelUniverse` both of which must conform to `GodsWill`. They all use the default implementation of `conforms()` but their myriad subclasses must customize that implementation. – mogelbuster Oct 23 '16 at 14:23
  • I suppose I could make `GodsWill` a base class to the universe classes, instead of a protocol, but this would cut back on code reuse and extensibility of `GodsWill`, which is exactly the purpose of swift protocol extensions. Alternatively, I could make a new base class `TheMultiverse` which is the superclass to all the universes, delete the protocol extension of `GodsWill` (but keep the protocol itself) and move the default implementation of `conforms()` inside `TheMultiverse`. But if I have to do all that, then what is the purpose of protocol extensions? It seems like a swift design flaw. – mogelbuster Oct 23 '16 at 14:49