I'm a software developer based in the UK. I am blogging regularly about software development & Apple

Dispatch rules for classes & protocol extensions

A few weeks back I ran into an issue with protocols. Depending on the type of a variable, protocol or class, different methods were called. Hence today I would like to talk about Swift's dispatch rules for classes and protocol extensions.

Dispatch rules for classes & protocols

To start with, let's define a protocol with a default implementation and a class:

protocol Printable {
    func printMe() -> String
}
extension Printable {
    func printMe() -> String {
        return "Me:This is Printable"
    }
}
class P : Printable {
    func printMe() -> String {
        return "Me:This is P"
    }
}

In the following I'd like to know what happens if we

  • instantiate a class and call a method on that class
  • instantiate a protocol and call a method on that protocol
  • cast the class to a protocol and call a method on that protocol

So what happens in those three cases with our current class and protocol?

let p1 : P = P()                  // p1:Me:This is P
let p2: Printable = P()     // p2:Me:This is P
let p3 = p1 as Printable// p2:Me:This is P

I guess, this is no surprise at all. It's what everybody would expect. The class overrides the default protocol implementation no matter if used as a protocol or a class.

Let's add a method to our protocol extension and override that method in our class

extension Printable {
    func printMe2() -> String {
        return "Me2:This is Printable"
    }
}
extension P {
    func printMe2() -> String {
        return "Me2:This is P"
    }
}

What's the outcome now? Let's see again:

let p1 : P = P()                  // p1:Me2:This is P
let p2: Printable = P()     // p2:Me2:This is Printable
let p3 = p1 as Printable   // p2:Me2:This is Printable

What happened? Let's recap. When dealing with the class, the class overrides the default implementation. So far, so good. But when dealing with the protocol, no matter if instantiated as a protocol or first as a class and then cast to a protocol, the protocol's default implementation will be called. Thinking about it, I guess it makes sense that the last two rows show the same outcome. When we are casting the class to a protocol, the class type will be replaced by the protocol type. A typical case of type erasure. But what's the rule here? Why is the class method called in the first case but not in the other two? As it turns out

When dealing with protocols, if the called method does only exist in the protocol extension but not in the protocol definition, then the protocol's default implementation in the extension will be called.

And for classes we have:

When dealing with classes, if the called method is defined in the class and in the protocol extension then the class always overrides the protocol's default implementation.

Conclusion

Protocol extensions help us to keep our code clean and DRY. But we need to be aware of the dispatch rules that accompany them. Otherwise we are in for a surprise.

If you want to play with the sample code to memorize the rules, I created a sample playground which you can find here.

GCD with Swift 3

Migrating to Swift 3 & iOS 10