Protocols, Default Implementations and Class Inheritance

Say you have the following setup:

  • A protocol named Doable that defines the doSomething() method.
  • A default implementation for the doSomething() method in a protocol extension.
  • A base class that conforms to Doable, but does not implement the doSomething() method itself.
  • A sub-class inheriting from the base class which provides a custom implementation of the doSomething() method.
  • An array of mixed base and subclass instances that is type [Doable].

The results of invoking doSomething() on all elements of the array may surprise you. When the for loop / reduce / whatever invokes doSomething() on a member of the array which is a subclass, you will not get the subclass’ custom implementation. Instead, you will get the default implementation!

When the runtime goes looking for doSomething() on the current object (of type Doable) in the loop, it looks to the object which actually conforms to the Doable protocol, which is the base class. The runtime checks to see if the class implements the method, and when it sees that the base class does not, it falls back to the default implementation, rather than seeing if the subclass implements it. Apparently, the subclass is only checked in instances where it is overriding a method explicitly defined on its superclass.

So, the solution is actually quite simple:

  • Provide an implementation of doSomething() on the base class. It can just be a duplicate of the default implementation, if that’s the behavior you want for it.
  • Change the subclass’ doSomething() implementation to include an override declaration.

That’s it! The next time you run your loop, the sub-class will have it’s doSomething() method called. I made a playground for you to check this out (turn on documentation rendering):

//: # Protocols and Inheritance
//: This playground demonstrates a gotcha in the use of protcols with default
//: method implementations along with class inheritance.
import UIKit
//: This protocol defines a single method that takes an `Int` and returns an `Int`.
protocol Doable {
func doSomething() -> Int
}
//: This extension provides a default implementation which simply returns 1.
extension Doable {
func doSomething() -> Int {
print("Default doSomething implementation called.")
return 1
}
}
//: The `Base` class conforms to Queryable, but does not implement the
//: `doSomething(value:)` method, relying on the default implementation.
class Base: Doable {
}
//: The `Sub` class inherits from `Base` and adds its own implementation of the
//: `doSomething(value:)` protocol function.
class Sub: Base {
/// This custom impelmentation returns the stored itemCount.
func doSomething() -> Int {
print("Subclass implementation of doSomething() called.")
return 5
}
}
//: Now, let's build a list of objects conforming to Queryable, including 1 `Base` instance.
var doList: [Doable] = []
doList.append(Base())
doList.append(Sub())
doList.append(Base())
doList.append(Base())
doList.append(Base())
//: You might expect this reduce call, which adds up the result of invoking `doSomething`
//: on each of the Queryable objects would return a result of 8.
let sum = doList.reduce(0, { (count, query) -> Int in
let itemCount = query.doSomething()
return count + itemCount
})
//: However, we get 5, and you can see in the console that the print statement in the
//: `Sub` implementation of `doSomething` is never called.
print("Final sum: \(sum)")
//: This is because `Sub` does not conform to `Queryable`, `Base` does. When the resolver
//: checks if `Sub` has an implementation of `doSomething` and it does not, the resolver
//: uses the default implementation instead.
//:
//: In order to make this work as expected:
//: * Add the `doSomething` method to `Base` (it can just return 1).
//: * Change the signature of `doSomething` in `Sub` to include the `override` keyword.
//:
//: ### Try it out!