Swift Protocol Extension Weirdness

Swift is a protocol oriented language, and I’ve found I can really provide a lot of power as well as flexibility by making good use of protocols.

The other day I was chasing down a confusing bug. For some reason my protocol methods weren’t being called!

It turns out protocol extension methods can be unintuitive sometimes.

Protocols Acting According to Protocol

What makes protocols great is that you separate declaration from definition – you can have different types conform to the same protocol, but they might implement the functions in their own way.

Swift 2 gave us protocol extensions, and we can add some default behavior to our protocols.

protocol Ferocious {
    func roar()
}

extension Ferocious {
    func roar() {
        print("ROOOAAARRR!!!")
    }
}

struct Dinosaur: Ferocious {
    // We are fine with the default roar
}

struct Dog: Ferocious {
    // We don't want the default, so we define our own
    func roar() {
        print("woof!")
    }
}

let dinosaur: Dinosaur = Dinosaur()
dinosaur.roar() // "ROOOAAARRR!!!"

let dog: Dog = Dog()
dog.roar() // "woof!"

let ferociousDog: Ferocious = Dog()
ferociousDog.roar() // "woof!"

In this case, it acts exactly as we expect. A Dinosaur will be called with the default implementation. A Dog will call Dog’s implementation of it, whether or not it is being treated as a Dog or a Ferocious. It is a Dog, after all.

Protocols Breaking Protocol

But then what if we wanted to add some more functionality to Ferocious? What would happen if we added a bite() method to our protocol extension?

protocol Ferocious {
    func roar()
}

extension Ferocious {
    func roar() {
        print("ROOOAAARRR!!!")
    }

    func bite() {
        print("BITE!!!")
    }
}

But we don’t want our Dog to bite in capital letters! Our app’s only requires cute puppies.

Puppy in Mug Cup

Watch out, he’s actually got quite a bite!

So we’ll just make him nom a bit:

struct Dog: Ferocious {
    // We don't want the default, so we define our own
    func roar() {
        print("woof!")
    }

    func bite() {
        print("nom nom nom")
    }
}

Alright, let’s check out the new functionality we added to our Ferocious creatures.

let dinosaur: Dinosaur = Dinosaur()
dinosaur.bite() // "BITE!!!"

let dog: Dog = Dog()
dog.bite() // "nom nom nom"

let ferociousDog: Ferocious = Dog()
ferociousDog.bite() // "BITE!!!"

Okay, it seems like something is wrong here. The Dinosaur is alright, because it’s just using the default implementation. But our two Dogs are behaving differently… That’s odd…

Did you catch the difference? The dog is defined as a Dog type, whereas ferociousDog is defined only as a Ferocious type.

However, if we move the bite function out of the extension and into the protocol, we can see that it behaves as we originally wanted it to:

protocol Ferocious {
    func roar()
    func bite()
}

...

let dinosaur: Dinosaur = Dinosaur()
dinosaur.bite() // "BITE!!!"

let dog: Dog = Dog()
dog.bite() // "nom nom nom"

let ferociousDog: Ferocious = Dog()
ferociousDog.bite() // "nom nom nom!!!"

There we go, that’s more like what we expected.

Why??

While confusing at first, there is some logic to this behavior.

If you have the method in the protocol section, then it is clear that there is a method that the protocol provides.

If you only have it in the protocol extension and your object’s implementation, it’s just an overloaded method. Whichever one is appropriate will run.

That’s why when we declared a Dog instance, we called Dog methods, and when we declared a Ferocious instance, we called Ferocious instances.

Conclusion

Put all your protocol methods in the protocol section. If you need some helper extension functions, make them private so you don’t accidentally call the wrong implementation.

That’s my recommendation, but if you can think of any situation where it makes sense to use those extension methods, leave a comment! I’d love to hear it.

Update

@NatashaTheRobot told me on Twitter that this behavior is an intended feature of the language! It can help adding a kind of mixin functionality. She showed me an example using it for Segue Identifiers that seems to make sense.

It is also mentioned in the WWDC talk about Protocol-Oriented Programming. The relevant bits start at around 29:30.

[Protocol] Requirements create customization points … Should everything in your protocol extension also be backed by a requirement? Not necessarily. Some APIs are just not intended as customization points.

I think that’s really the key point right there. Make your protocol method a requirement if it’s meant for customization.

And try not to shadow your non-required protocol methods! You could end up with a confusing bug on your hands!