CloseHomeAboutBlogCourse
Bob I’m an iOS instructor/blogger from S.Korea.July 14 • 5 min read • Edit

Generic Protocols with Associated Type

Learn how to create generic protocol and override associated type

Introduction

You’ve started learning Protocols and you’ve got addicted to it. One day, you heard some words like associated type and type eraser. It seems rough and wild. I feel you. So, I wrote this tutorial for you. Let’s get started.

Goals

There are two objectives. First, learn how to create a generic protocol with associatedtype. Second, use where clauses for type constraints similar to that of generics.

Prerequisites

I consider this tutorial as somewhat intermediate-advanced. So, I expect you to be familiar with the topics below.

Intro to Generics, Intro to Protocol Oriented Programming, Closure, Typealias.

If you are not comfortable with the topics above, you might find Learn Swift with Bob useful.

The Number One Rules

The Swift Programming Language is considered as type-safe. It means, the type must be defined before compiled.

Review

Before we dive into generic protocols, you should be familiar with the following code below.

struct GenericStruct<T> {
 var property: T?
}

I could either explicitly state the type of T or let Swift infer based on the value.

let explictStruct = GenericStruct<Bool>()
// T is Bool
let implicitStruct = GenericStruct(property: "Bob")
// T is String

Keep in mind of the principle that every type must be defined.

Normal Protocol

First, to appreciate generic protocols, let’s look into your/my past. Let’s create a protocol that requires you to add property whose type is String.

Design Protocol

protocol NormalProtocol {
 var property: String { get set }
}

Design Class and Conform

class NormalClass: NormalProtocol {
 var property: String = "Bob"
}

Sounds good. However, NormalProtocol forces NormalClass to work with String. But, what if you want property to be Int or Bool?

It’s time to introduce Protocol Associated Types. 😎 (I should have a thug-life meme)

Introducing Protocol Associated Types

In generic protocols, to create something like <T> in generics, you need to add associatedtype.

protocol GenericProtocol {
 associatedtype myType
 var anyProperty: myType { get set }
}

Associated type = type alias + generics

Now, anything that conforms to GenericProtocol must implement anyProperty. However, the type is not defined. Therefore, the class or struct that conforms to the protocol must define it either implicitly or explicitly.

First, let’s create a class SomeClass that conforms to GenericProtocol. We must define myType. Well, there are two ways to define as stated above.

Define Associated Type Implicitly

You may define myType based on the value associated with anyProperty.

class SomeClass: GenericProtocol {
 var anyProperty: myType = "Bob"
}

Now, myType has been defined as String based on “Bob”. However, you can let Swift do more guessing as shown below.

class SomeClass: GenericProtocol {
 var anyProperty = "Bob" // myType is "String"
}

Is everything okay with you? If so far so good, you can show me some love through ❤️.

Define Associated Type Explicitly

Well, you may also define the associated type, myType by calling typealias. What? Let’s take a look.

class SomeClass: GenericProtocol {
 typealias myType = String
 var anyProperty: myType = "Bob"
}

If you want to define the associatedtype a.k.a myType, you may use typealias. Of course, it is not necessary since you may define myType implicitly as we’ve seen.

So far, you’ve defined myType as String. Let’s create a struct that conforms to GenericProtocol but myType is Int instead.

struct SomeStruct: GenericProtocol {
 var anyProperty = 1996
}

You’ve implicitly stated that myType is Int based on the value of 1996.

If you hear Protocol Associated Types (PATs), it just means generic protocols.

Protocol Extension and Type Constraints

As you already know, Protocol extension is amazing because it provides default implementations without having to define required methods and properties. Let’s review.

Design Extension
extension GenericProtocol {
 static func introduce() {
  print("I'm Bob")
 }
}

Anything that adopts GenericProtocol now contains this magic.

SomeClass.introduce() // I'm Bob
SomeStruct.introduce() // I'm Bob

But all of a sudden, you only want myType as String to have the introduce()method. How do you go about?

Introducing Where Clause

Don’t worry if you have never used where. It’s just a shorter way to write an else-if statement.

Let’s s add introduce() for those who not only conform to GenericProtocol but also has the associatedtype ,a.k.a myType as String.

extension GenericProtocol where myType == String {
 func introduce(){
  print("I'm Bob")
 }
}

The where clause above state, if myType is String, proceed, if not ignore the entire extension block.

Let’s Test

If you remember, SomeClass has String and SomeStruct has Int.

let someClassInstance = SomeClass().introduce() // "I'm Bob"

Let’s attempt for SomeStruct.

let someStructInstance = SomeStruct() // Error

Multiple Where Conditions with Self

You may add multiple where clauses to make the extension more specific. All you have to do is just add , and more conditions after.

This time, we will add one more constraint that onlySomeClass may have the introduce() method.

extension GenericProtocol where type == String, Self == SomeClass {
 func introduce(){
  print("I'm Bob")
 }
}

The Self refers to the struct/class/enum that conforms GenericProtocol. As a result, only SomeClass will have the introduce() method.

So far so good?

Override Associated Type

So far, in GenericProtocol, we have not defined associatedtype within the protocol itself.

protocol GenericProtocol {
 associatedtype myType
 var anyProperty: myType { get set }
}

The type of myType has been defined by those who conform to the protocol. However, you may also pre-defined associatedtype within a protocol as well.

Associated Type Pre-Defined Protocol

Let’s create a protocol called, Familiable. It contains an associatedtype called FamilyType. But, you’ve pre-defined its type as Int.

protocol Familiable {
 associatedtype FamilyType = Int
 func getName() -> [FamilyType]
}

It somewhat looks like a typealias.

Adopt Type Pre-defined Protocol

class NumberFamily: Familiable {
 func getName() -> [FamilyType] {
  return [1, 2, 3]
 }
}

or

class NumberFamily: Familiable {
 func getName() -> [Int] {
  return [1, 2, 3]
 }
}

Now, if you create an instance,

let numberFam = NumberFamily() // NumberFamily<Int>

However, it is possible to override/change the pre-defined type of a protocol.

Override Associated Type

First, we will create a generic struct called, NormalFamily. It conforms to Familiable. That means Familiable will force the struct to work with Int. However, you refuse.

You want your struct to work with String since a normal family should have names like “Bob” or “Bobby” instead of 1, 2, 3.

struct NormalFamily<T: ExpressibleByStringLiteral>: Familiable  {
 func getName() -> [T] {
  return ["Bob", "Bobby", "Lee"]
 }
}

Now, if you create an instance,

let normalFam = NormalFamily() // NormalFamily<String>

How is this possible? Well, if you option-click on String in Swift, you will discover, String conforms to ExpressibleByStringLiteral.

// Swift Library
extension String : ExpressibleByStringLiteral {}

Source Code

Last Remarks

In this tutorial, you’ve learned how to override associatedtype and even combine protocols with generics. How was your learning going? We’ve talked about a lot of theories in this article. Of course, if you want to learn practical Protocol Oriented Programming, RxSwift, MVVM, I highly recommend you to join my mailing list.

About Me

iOS Developer from South Korea. Feel free to follow my story on Instagram or get serious on LinkedIn