top of page

Mastering Swift's Development Blog

Follow Us On Twitter
  • Writer's pictureJon Hoffman

Swift Protocols Part 4: Associated Types with Protocols

Updated: Sep 4, 2022


Protocols are the core that the Swift programming language is built upon. Understanding how they work and what makes them so powerful is essential to understanding the language itself. The protocol is so important to the Swift language that we have create a four-part series on it. In this fourth and final post we will examine associated types with protocols and see what makes them so powerful. We will also see how we can use the where clause with associated types and protocols.


Associated Types

An associated type can be seen as a place holder for a specific type within a protocol’s definition. When a protocol is adopted the exact type that the associate type represents will need to be specified. Associated types enables us to define a protocol without having to specify the specific type.


Let’s look at how we would define a Queue protocol without associated types and then how we would define the same protocol with associated types. Here is the Queue protocol without associated types.

protocol Queue {
    func enqueue(_ item: String)
    func dequeue() -> String
}

You can probably see why this would be problematic. When we define the String type within the enqueue and dequeuefunctions, we are forcing any type that uses this protocol to only using the String type for the queue however what if we wanted our queue to contain integers or some custom type. This is where associated types come in. Let’s see how we can use associated types to make a more flexible Queue protocol

protocol Queue {
    associatedtype ItemType
    func enqueue(_ item: ItemType)
    func dequeue() -> ItemType
}

The first line of the protocol defines an associated type using the associatedtype keyword. We name this associated type ItemType and we then use it in the enqueue() and dequeue() functions. We can now adopt this protocol like this:

struct MyQueue: Queue {
    func enqueue(_ item: Int) {
             //code
    }
    
    func dequeue() -> Int {
             //code
    } 
}

You will notice that we do not have to define the type to use in place of the associated type instead the compiler knows it is an integer based on the function’s definitions. One thing to note is; the same type must be used everywhere in the implementation that the associated type is use in the protocol otherwise we will get an error. For example, the compiler will show us an error if we used an integer type in the enqueue() function and a String type in the dequeue() function.


We do not need to provide a concrete type in place of the associated type, we can use generics as shown here

struct MyQueue2<T>: Queue {
    func enqueue(_ item: T) {
        //code
    }
    
    func dequeue() -> T {
        //code
    }
}

Associated types enables us to create generic types while still adhering to the protocol first design paradigm. Now let’s look at protocol inheritance with associated types because this can be a gotcha if you are not aware of how it works. The first thing to remember is when you adopt or inherit from a protocol you must define the type for the associated type. When a protocol inherits from another protocol that has an associated type, you should define the type for the associated type with the where statement.


Where Statements With Associated Types


To demonstrate how to use the where statement, lets look at how we make define the hierarchy for a vacation reservation site where we are able to make reservations for airline flights, car rentals and hotel rooms. We will start off by defining three concrete types (we are eliminating all of the code within these types so we can focus on the concepts of using the associated type and not the code itself).

struct Flight {}
struct CarRental {}
struct Hotel {}

We will now define a ReservationItem protocol that other reservation protocols will inherit requirements from.

protocol ReservationItem {
    associatedtype ReservationType 
    func reserve(_ item: ReservationType)
}

Now we will make three protocols which will define the requirements for types that are used to make flight, car rental and hotel reservations. These types will inherit the requirements in the ReservationItem protocol and will define the type to use for the ReservationType associated type using the where statement like this.

protocol FlightReservation: ReservationItem where ReservationType == Flight {
  //Flight specific features
}
protocol CarRentalReservation: ReservationItem where ReservationType == CarRental {
   //Car Rental specific features
}
protocol HotelReservation: ReservationItem where ReservationType == Hotel {
   //Hotel specific features
}

Notice that after the protocol definition and specifying that we are inheriting from the ReservationItem protocol, we have the where clause which defines what type to use for the ReservationType associated type.


Using associated types with our protocols enables us to make our code more flexible and easier to maintain. Keep in mind, any time we can make our code more flexible and easier to maintain we should.


This is the final post in our four-part series on the protocol. Protocols can be very powerful when used correctly. We are putting together a series on Protocol Oriented Design Patterns which will show you how to take traditional design patterns and implement them in a protocol oriented way. These design patterns posts will demonstrate the techniques from these four posts and show how to use protocols to develop very flexible and easy to maintain code. In the meantime, here are the other posts in our Swift Protocol series


masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt

bottom of page