Matua Doc

Matua Doc

Protocols

What we know: structs

We can use struct to define our models, they act like blueprints.

struct Car {
   let brand: String
   let model: String
   let year: Int
}

Using that struct, we can then create instances of objects.

let joesCar = Car(brand: "Porsche", model: "911", year: 2025)
let docsCar = Car(brand: "Lamborghini", model: "Aventador", year: 2026)

What we know - methods

We can also add methods to our structs to add functionality.

struct Car {
   let brand: String
   let model: String
   let year: Int
   func description() -> String {
       return "\(year) \(brand) \(model)"
   }
}

let joesCar = Car(brand: "Porsche", model: "911", year: 2025)
print(joesCar.description())

Let’s extend

We’ve got a car, let’s make a motorbike using a new object.

struct Motorbike {
   let brand: String
   let model: String
   let year: Int
   func description() -> String {
       return "\(year) \(brand) \(model)"
   }
}

let joesCar = Car(brand: "Porsche", model: "911", year: 2025)
let joesMotorbike = 
Motorbike(brand: "Kawasaki", model: "Ninja H2", year: 2025)

If we wanted to store all of my vehicles in a collection that I can print anytime, how could we do it?

let vehicles = [joesCar, joesMotorbike]
  • Swift would allow Any, but we lose type safety — Swift no longer knows what methods are available.
  • Swift wants a guarantee that everything in this list can do the same thing.

How can we give this guarantee?

Protocols

A protocol is a contract that describes required properties, methods, or operators, and conformance means a type provides those requirements.

  • Just like our struct defines a blueprint for objects, a protocol defines a blueprint for methods and properties
  • A protocol can then be adopted by a struct, class, or enum to provide an actual implementation of those requirements.
  • Any type that satisfies the requirements of a protocol is said to conform to that protocol.
  • With protocols, we can define what a type can do and now different models (Car, Motorbike) can share behaviour (being describable) through common interfaces

Protocols

  • Let’s update our vehicle models…
  1. Start by defining our new Protocol:
protocol Describable {
   func description() -> String
}
  1. Now we ask our models, our structs, to conform to our protocol:
struct Car: Describable {
   let brand: String
   ...
   func description() -> String {
       return "\(year) \(brand) \(model)"
   }
}

struct Motorbike: Describable {
   let brand: String
   ...
   func description() -> String {
       return "\(year) \(brand) \(model)"
   }
}
  1. Now we can create out collection of vehicles and print them out!
let vehicles: [Describable] = [joesCar, joesMotorbike]

vehicles.forEach { vehicle in
   print(vehicle.description())
}
  1. Now what happens when we create a new vehicle type, but don’t conform to the protocol? Why does this new Bus model not conform?
struct Bus: Describable {
   let magic: Bool
   let amountOfWheels: Int
}

Swift stops us in our tracks and forces our model to conform.

Pre-defined protocols

We have just defined our own protocol, but there are a bunch of pre-defined, commonly used protocols we can utilise.

  • CustomStringConvertible - Better debug & display output
  • Equatable - value comparison
  • Comparable - sorting
  • Identifiable - creating a stable identity
  • Codable - encoding/decoding for storage and networking

CustomStringConvertible

The CustomStringConvertible protocol lets a type provide a readable description string as a computed property.

Oh - that’s literally what we were just doing! So…

struct Car: CustomStringConvertible {
    …
   var description: String {
       "\(year) \(brand) \(model)"
   }
}

let joesCar = Car(brand: "Porsche", model: "911", year: 2025)
print(joesCar)

Our Car struct conforms to this protocol by defining the description property.

Equatable

How do you define if something is equal to something else?

Sometimes it’s simple. 1 is equal to 1. "Apple" is equal to "Apple". How about this:

struct Student {
   var name: String
   var englishGrade: String
   var mathsGrade: String
}
var joe = Student(name: "Joe", englishGrade: "C", mathsGrade: "B")
var jim = Student(name: "Jim", englishGrade: "C", mathsGrade: "B")

Are joe and jim equal?

The Equatable Protocol defines whether two values should be treated as equal by implementing ==

struct Student: Equatable {
   var name: String
   var englishGrade: String
   var mathsGrade: String

   static func == (lhs: Student, rhs: Student) -> Bool {
       return (lhs.englishGrade == rhs.englishGrade) &&
               (lhs.mathsGrade == rhs.mathsGrade)
   }
}

In the example above, we define equality in our Student when their english and maths grades are the same.

Comparable

The Comparable Protocol extends Equatable and adds ordering rules, so that values can be sorted.

struct Score: Comparable {
   var player: String
   var points: Int
   static func < (lhs: Score, rhs: Score) -> Bool {
       lhs.points < rhs.points
   }
}
let scores = [
   Score(player: "Mia", points: 25),
   Score(player: "Noah", points: 18),
   Score(player: "Zoe", points: 30)
]
let sortedScores = scores.sorted()

Here, we conform by adding the < function and defining what makes one score lower than another.

Sets

Sets are a collection of unique values with no guaranteed order.

let tags: Set<String> = ["swift", "protocols", "swift"]
print(tags.count) // 2
print(tags.contains("protocols")) // true

Elements must conform to Hashable.

What does it mean to be Hashable?

Hashable

The Hashable protocol lets Swift generate a consistent hash value from a type’s data so it can be stored in a Set (or used as a dictionary key).

struct Room: Hashable {
   let building: String
   let number: Int

   func hash(into hasher: inout Hasher) {
       hasher.combine(building.lowercased())
       hasher.combine(number)
   }
}

What’s going on here?

Our hash for our Room struct is made from a combination of the building, lowercased, and combined with the number.

Codable and JSONEncoder

The Codable protocol helps us to convert models to and from JSON (JavaScript Object Notation). JSON is a data format commonly used in web contexts, often to transmit data between client and server. It is essentially a list of keys and values.

Here’s an example of JSON:

{
 "employees": [
   {
     "firstName": "Joe",
     "lastName": "Henshaw"
   },
   {
     "firstName": "Janet",
     "lastName": "Burns"
   },
 ]
 "department": "Engineering",
 "location": "Wellington"
}

This JSON represents a list of employees, a department and a location.

Here’s an example of a Lesson struct conforming to the Codable protocol:

struct Lesson: Codable {
   var id: Int
   var title: String
   var isPublished: Bool
}
let lesson = Lesson(id: 1, title: "Protocols", isPublished: true)
let data = try JSONEncoder().encode(lesson)
let decoded = try JSONDecoder().decode(Lesson.self, from: data)

You can see in the JSON example above, we have keys employees, firstName, lastName, department and location. We can specify how we could like these keys to be encoded and decoded using CodingKeys as so:

struct StudentProfile: Codable {
   let id: UUID
   let fullName: String

   enum CodingKeys: String, CodingKey {
       case id
       case fullName = "full_name"
   }
}

This is helpful when external property names differ from the property names in your program.

Identifiable

The Identifiable protocol ensures that types possess a stable, unique identifier (id) so that Swift can efficiently manage items in collections. The id property is commonly implemented using UUID.

struct StudentProfile: Identifiable {
   let id: Int
   let fullName: String
}

Tasks - SchoolSystem Part 1

  1. Create a Student that conforms to Identifiable
    • Properties: id, name, age
  2. Create a Course that conforms to CustomStringConvertible
    • Properties: id, title, courseDescription
    • Description should print a readable summary line
  3. Create an Enrolment that conforms to Codable
    • Properties: studentId, courseId
    • Encode one submission with JSONEncoder and decode it with JSONDecoder to make sure it works

Tasks - SchoolSystem Part 2

  1. Create ScoreEntry that conforms to Comparable
    • Properties: studentId, points
    • Implement < using points
    • Create an array and print entries sorted from lowest to highest
  2. Update Enrolment to conform to Hashable
    • Implement an enrolment hash to be the studentId and courseId connected by a full stop. e.g. 93CD01EF-5D5D-456A-93AD-FCD7D98F59B3.SWE13
    • Build a Set of Enrolments with duplicates and print the unique count
  3. Update ScoreEntry to conform to Equatable.
    • Implement == so that ScoreEntrys are equal when the points are equal regardless of the studentId

Tasks - SchoolSystem Part 3

  1. In the main test flow, print:
    • Create two Students
      • Name: Jules, Age: 16
      • Name: Stan, Age: 17
    • Create a Course
      • Title: 13SWE, Course Description: “Sweet Food in Hospitality”
    • Create an Enrolment: Enrol Stan and Jules in 13SWE
      • Encode and decode the enrolment. Print the HASH of the decoded enrolment
    • Create another Enrolment: Enrol Stan again in 13SWE using a new Enrolment object
      • Add all enrolment objects to a Set and print out the Set Count
    • Create two ScoreEntry
      • StudentId: Jules ID, Points: 55
      • StudentId: Stans ID, Points: 50
      • Add both ScoreEntry to an array and sort it. Print the results.
    • Create a new Student called Ash, Age 18, and create a ScoreEntry for Ash with 55 points
      • Compare Ash’s ScoreEntry with Jules’ ScoreEntry. Confirm equality using == and
      • print the result

Tasks - SchoolSystem Extension!

Update your program so that every struct conforms to more than one protocol. Keep the original protocol and add the following:

  • Student: CustomStringConvertible, Codable
  • Course: Identifiable, Equatable, Codable
  • Enrolment: Identifiable, Equatable, CustomStringConvertible
  • ScoreEntry: Comparable, Equatable, Codable

Test each struct with at least two behaviours you’ve added. If you’re feeling adventurous, attempt to encode and decode each of your objects.

Summary

Protocol conformance in Swift lets types participate in powerful shared behaviours.

  • CustomStringConvertible improves output readability,
  • Equatable and Comparable support logic and sorting,
  • Hashable enables set and dictionary use,
  • Codable supports data exchange,
  • Identifiable supports stable identity.

These built-in protocols help your models stay expressive, reusable, and compatible with Swift’s ecosystem.

Review

Use these prompts to check your understanding.

  • Explain protocol conformance in one clear sentence,
  • describe when to choose CustomStringConvertible,
  • compare Equatable and Comparable,
  • explain why Hashable is needed for Set,
  • and name one real project situation where Codable is essential.