Associated Types in Swift

Or how to create amazing generic protocols in Swift.

Joshua Brunhuber
joshtastic-blog
4 min readMay 20, 2018

--

TL;DR: They are like generics for protocols with which you can do great stuff.

Summer finally arrived

Motivation

Reusability is a great and essential part in software development. In my opinion one of the most important thing to consider in software architecture is the implementation time for upcoming features. I often have moments then I lie in my bed and can’t sleep until the damn feature got implemented. So a fast implementation time can save me some huge amount of sleeping hours (which I absolutely need).

Inheritance is a way to reuse functionality from super-classes. But things can grow very quickly and with it the complexity rises.

Fortunately we have the protocol oriented approach in Swift. But protocols have to be generic in order to reuse their definitions with different types. Classes allow us to specify the generic parameters. Protocols offer us „associated types“.

Real-Life Example: CRUD protocol for CoreData handling

I want to create a protocol for my CoreData implementation. This will be the common CRUD (create, read, update, delete) functionality. Without a generic protocol I have to use NSManagedObject everywhere which is really uncomfortable because I have the Xcode generated subclasses. My entities are: TaskMO and ItemMO. I need manager classes for both of them. The protocol definition have to match once for the TaskMO and once for the ItemMO. This can be implemented using the „associatedtype“ Keyword.

protocol Persistable {    associatedtype Entity}

As you can see here Entity is just a placeholder and not a concrete type. The concrete type will be specified only in the implementation.

Using constraints to narrow the type

The Persistable protocol allows us to handle the CRUD functionality. But I want to ensure that it just will be used for CoreData purposes. We can ensure this by adding constraints to our associated type. In my case I just want to allow the usage on NSManagedObject subclasses. This step is very similar to the generic parameters.

protocol Persistable {    associatedtype Entity: NSManagedObject}

This is how the full protocol looks like:

Writing the implementation

Now implement the types just as you’re used to it. A compiler feature named „type inference“ automatically detects the definition of the concrete type. So we don’t have to specify it explicitly.

Making a coffee machine

Cool. We created a generic reusable protocol. But let’s dive a bit deeper. I’d like to create a coffee machine. Not just basic black coffee. I want a great latte macchiato.

At first, I specify the ingredients:

My coffee consists of the primary coffee ingredient and some sort of milk. I named the protocol „Drinkable“. Everything what is drinkable has ingredients. A drinkable is able to fill itself (oh, wouldn’t this be gerat in real life? ☕️), and has two provide methods. One for the primary coffee ingredient and one for the milk-like ingredient.

The fill method will do the same thing every time. It puts the coffee and the milk part in the cup. No matter which coffee we choose. So lets create a default implementation via a protocol extension.

extension Drinkable {    mutating func fill() {

ingredients.append([provideMilk(), provideCoffee()])
}
}

And most of my coffee consists of espresso. So it’s useful to create a default implementation for it too.

func provideCoffee() -> Espresso {

return Espresso()
}

It’s a bit more difficult with the milk. Maybe you have some vegan guys over. There you have to choose soy-milk instead of traditional milk. But the process is every time the same. Put the milk into the coffee. So I write another default implementation for this case too but this time I use the generic associated type instead of the concrete one.

The difference is that the primary ingredient (Espresso, the concrete type) will be automatically used in the implementations. Because we’re using the associated type for the milk-like ingredient we have to specify the type in all of our implementations explicitly.

Now I create the different types of coffee I serve. At first my favorite: The classic Latté Macchiato. It will be made with traditional milk.

struct LatteMachiato: Drinkable {    var ingredients: [Any] = []    typealias SecondaryIngredient = Milk}

The typealias definition tells the compiler that our generic associated type „SecondaryIngredient“ is now the concrete Type „Milk“. So the machine will be using „Milk“ for our Latté Macchiato.

Now the Soy-Latté. This time I use soy-milk.

struct SoyLatte: Drinkable {    var ingredients: [Any] = []    typealias SecondaryIngredient = Soy}

Finally. Let’s make coffee.

Latte Macchiato: [[__lldb_expr_1.Milk(ingredient: “🥛”), __lldb_expr_1.Espresso(ingredient: “☕️”)]]Soy Latté: [[__lldb_expr_1.Soy(ingredient: “🌱”), __lldb_expr_1.Espresso(ingredient: “☕️”)]]

As you can see in the output, the soy latte uses the „Soy“ type because we specified it with the typealias. The great thing is that we abstracted the „fill“ method and concretised the type to „Soy“ in the implementation.

Conclusion

Associated types allows us to create more adaptable and flexible apps. And with it the implementation time shrinks dramatically. I made the experience in my last app PursCreate while refactoring from spaghetti to a modular architecture.

That’s it for today. Now it’s time for a good coffee ☕️

Do you have any questions, improvement suggestions or just wanna be my best friend? Catch me up on twitter: https://twitter.com/jbrunhuber.

--

--

Joshua Brunhuber
joshtastic-blog

iOS Developer📱 Nature🌲🍂 I also like music 🎸 and photography 📷