When defining a protocol, there are times where it is useful to define one or more associated types. An associated type gives us a placeholder name that we can use within the protocol in place of a type. The actual type to use for the associated type is not defined until the protocol is adopted. The associated type basically says, We do not know the exact type to use; therefore when a type adopts this protocol it will define it. As an example, if we were to define a protocol for a queue, we would want the type that adopts the protocol to define the instance types that the queue contains rather than the protocol.
To define an associated type we use the typealias
keyword. Let's see how to use associated types within a protocol. In this example, we will define the QueueProtocol
protocol that will define the requirements needed to implement a queue:
protocol QueueProtocol { typealias QueueType mutating func addItem(item: QueueType) mutating func getItem() -> QueueType? func count() -> Int }
In this protocol, we define one associated type named QueueType
. We then use this associated type twice within the protocol, once as the parameter type for the addItem()
method. We then use it again when we define the return type of the getItem()
method as an optional type that might return an instance defined by the QueueType
associated type; alternatively, it might return nil
.
Any type that implements the QueueProtocol
protocol must specify the type to use for the QueueType
placeholder and must also ensure that only items of that type are used where the protocol uses the QueueType
placeholder.
Let's look at how to implement QueueProtocol
in a non-generic class called IntQueue
. This class will implement the QueueProtocol
protocol using the Int
type:
struct IntQueue: QueueProtocol { var items = [Int]() mutating func addItem(item: Int) { items.append(item) } mutating func getItem() -> Int? { if items.count > 0 { return items.removeAtIndex(0) } else { return nil } } func count() -> Int { return items.count } }
As we can see in the IntQueue
structure, we use the Int
type for both the parameter type of the addItem()
method and the return type of the getItem()
method. These are the same types that were defined as the QueueType typealias
in the QueueProtocol
protocol.
In the preceding example, we implemented the QueueProtocol
protocol in a non-generic way. Generics in Swift allow us to define the type to use at runtime rather than compile time. Let's see how to implement the QueueProtocol
protocol as a generic type called GenericQueue
:
class GenericQueue<T>: QueueProtocol { var items = [T]() func addItem(item: T) { items.append(item) } func getItem() -> T? { if items.count > 0 { return items.removeAtIndex(0) } else { return nil } } func count() -> Int { return items.count } }
The GenericQueue
implementation is very similar to the IntQueue
implementation, except that we define the type to use with the generic placeholder T
. We can then use the GenericQueue
class as we would use any generic type. Let's take a look at how to use the GenericQueue
class:
var intQ2 = GenericQueue<Int>() intQ2.addItem(2) intQ2.addItem(4) print(intQ2.getItem()) intQ2.addItem(6)
Now that we have explored protocols in some detail, let's look at how we would implement the delegation design pattern using protocols.