← All courses

Protocols, Enums & Closures

🗓 May 31, 2026 ⏱ 3 min read

Protocols: contracts of behaviour

A protocol is a contract — a list of methods and properties a type promises to provide. It answers the question “what can this thing do?” without caring what it actually is. Protocols are the backbone of Swift’s design (the language is often called “protocol-oriented”).

protocol Shape {
    var area: Double { get }      // must provide this
    func describe() -> String
}

struct Circle: Shape {
    let radius: Double
    var area: Double { .pi * radius * radius }
    func describe() -> String { "Circle of area \(area)" }
}

Because both a Circle and a Square can conform to Shape, you can treat them uniformly — this is how you write flexible, reusable code.

The delegate pattern

UIKit uses protocols everywhere through the delegate pattern: one object hands off certain decisions to another. For example, a table view asks its delegate “how many rows?” and “what goes in this row?”. You’ll implement many delegates as an iOS developer.

protocol DownloadDelegate: AnyObject {
    func didFinish(_ data: Data)
}

class Downloader {
    weak var delegate: DownloadDelegate?   // weak avoids a retain cycle
    func finish(_ data: Data) { delegate?.didFinish(data) }
}

Enums: a fixed set of cases

An enum represents a value that can only be one of a known set of cases. Swift enums are unusually powerful: each case can carry its own data, and they pair perfectly with switch.

enum LoadingState {
    case idle
    case loading
    case success([User])     // carries data
    case failure(String)
}

func render(_ state: LoadingState) {
    switch state {
    case .idle:               break
    case .loading:            showSpinner()
    case .success(let users): show(users)     // unwrap the data
    case .failure(let msg):   showError(msg)
    }
}

Because the compiler knows every case, switch must be exhaustive — add a new case later and Swift points you to every place that needs updating.

Closures: functions you pass around

A closure is a block of code you can store and pass like a value — Swift’s version of a lambda. They power completion handlers, animations and functional methods like map.

let square = { (x: Int) -> Int in x * x }
print(square(5))    // 25

// trailing closure syntax (very common in iOS)
[1, 2, 3].forEach { number in
    print(number)
}

// completion handler
func loadData(completion: @escaping (Result<Data, Error>) -> Void) { }

@escaping means the closure may be stored and called later (after the function returns), which is typical for network callbacks.

The [weak self] capture

Closures capture the variables they use, which can create retain cycles if a closure captures self strongly. The fix is [weak self]:

downloader.completion = { [weak self] data in
    self?.update(with: data)
}

Common mistakes

  • Forgetting @escaping on stored/async closures.
  • Creating retain cycles by capturing self strongly in a closure — use [weak self].
  • Not making delegate properties weak.
Summary: Protocols define what a type can do (and power delegates); enums model a fixed set of cases that can carry data; closures pass behaviour around (mind @escaping and [weak self]). Together they appear in nearly every iOS app.