Protocol-Oriented Programming is awesome and powerful, we have no doubt about it! However Swift is still yet to be perfect (at least at the time this article’s being written), and one of the shortcoming of Swift that I want to talk about today is not able to perform dynamic dispatch when using PATs (Protocol with Associated Types) and how we can workaround this shortcoming and make PATs support dynamic dispatch.

What are PATs?

Associated type is a placeholder for an unknown type, and PATs are protocol that have one or more associated type requirement.

Animal protocol as shown below is an example of PATs.

If you would like to know more about PATs, I recommend you to read this awesome article by NatashaTheRobot.

What is Dynamic Dispatch?

Dynamic dispatch is the process of selecting which implementation of a polymorphic operation (method or function) to call at run time. — wikipedia

Sample code below demonstrate how we can achieve dynamic dispatch using protocol (without associated type).

Code above will generate 2 lines of output:

My Tiger is walking in the jungle.

My Cow is walking in the farm.

This shows that the walk() function calls have been dynamically dispatched to the desire concrete type implementation.

The Shortcoming of PATs

To demonstrate the shortcoming of PATs, let’s add an associated type requirement FoodType to our Animal protocol. We will also introduce eat(food: FoodType) function requirement to the Animal protocol.

If you paste the sample code above to your Xcode playground, you will get an error as shown below.

error: Protocol ‘Animal’ can only be used as a generic constraint because it has Self or associated type requirements

What does this mean? It basically means that we can’t use our Animal protocol as type anymore if our Animal protocol contains one or more associated type requirements.

But why??? 😢

Let’s take a look at the code snippet below.

Logically, we can say that code above is correct because we are feeding Meat to Tiger and Grass to Cow . However, there is no way for the compiler to know whether we are passing the correct FoodType type into the eat function. Both myTiger and myCow are Animal type, therefore the compiler have no idea which one is Cow type and which one is Tiger type. This kind of ambiguity explained why the compiler is stopping us from using Animal as type. Thus making dynamic dispatch impossible in this case.

The Workaround 💡

To workaround this we will use a technique called type erasure to hide dynamic dispatch within a type. To know more about type erasure, here’s an awesome talk by gwendolyn weston that you should not miss!

Back to our workaround, what we will do is stop using Animal protocol as type and introduce another new concrete type to help us on performing dynamic dispatch. Here we will name our new concrete type AnyAnimal , it is an enum that conform to the Animal protocol.

The code above is quite self explanatory, you can imagine that the AnyAnimal enum is a dispatcher that in charge of dispatching a function call to the desire Animal concrete type.

Do note that conforming AnyAnimal to Animal protocol is not mandatory here, however by doing so, we can reduce the robustness of our code. Imagine situation where new function requirement being added to Animal protocol in future, compilation error will trigger if we forget to update AnyAnimal protocol. Whereas no error will trigger if we do not conform AnyAnimal to Animal protocol.

Enough said! Let’s see AnyAnimal enum in action…

In code snippet above, we use AnyAnimal to replace Animal as our animalArray element type. With this we can avoid using Animal as type thus avoiding the error we previously encountered.

Next, let’s take a look at the output we get by enumerating each element in animalArray .

My Tiger is walking in the jungle.

My Tiger eat Meat

My Cow is walking in the farm.

My Cow eat Grass

We can see that the AnyAnimal enum is dispatching walk() and eat(food: FoodType) function call to the desire Animal concrete type correctly. 🎉 🎉

If you want to try out the code in your Xcode playground, you can get the full example code here.

Conclusion

The workaround in this article might not be ideal as it contain a lot of boilerplate code and we will have to handle all the dispatching logic manually. However, until the day Swift support existential, enums and type erasure might be the best tools that we can use to accomplish dynamic dispatch on PATs.

[Update]: 24 Oct 2017

Thanks to Hacker News user skue suggestion, we can actually improve AnyAnimal enum by changing it to be a generic struct.

With above generic struct, we will not need to update AnyAnimal manually every time when we add a new class that conform to Animal protocol. 👏👏👏

Further Reading