Skip to content
intermediate Phase 1 · Swift Foundations

Protocols & Extensions

Define protocols, protocol extensions, protocol-oriented programming, and composition.

50m
3 problems
Topic Progress 0%

Defining Protocols

Defining Protocols

A protocol defines a blueprint of requirements—properties, methods, and other constraints—that a type can adopt. Protocols are central to protocol-oriented programming (POP), which is Swift's preferred approach to polymorphism.

Basic Protocol

protocol Describable {
    var description: String { get }
    func describe() -> String
}

Adopting a Protocol

Any type (struct, class, enum) can adopt a protocol by implementing its requirements:

struct Person: Describable {
    var name: String
    var age: Int
    
    var description: String {
        return "\(name), age \(age)"
    }
    
    func describe() -> String {
        return "Person: \(description)"
    }
}

Protocol Properties

Protocols can require properties with specific access levels and whether they are gettable or gettable/settable:

protocol Identifiable {
    var id: String { get }  // read-only
}

protocol Mutable {
    var value: String { get set }  // read-write
}

struct User: Identifiable, Mutable {
    let id: String     // read-only satisfies { get }
    var value: String  // read-write satisfies { get set }
}

Protocol Methods

protocol Greetable {
    func greet() -> String
    mutating func reset()  // for value types
}

struct Greeter: Greetable {
    var count = 0
    
    func greet() -> String {
        return "Hello!"
    }
    
    mutating func reset() {
        count = 0
    }
}

The mutating keyword is needed when the method modifies the type (for structs and enums).

Protocol Initializers

protocol Decodable {
    init(from string: String)
}

struct User: Decodable {
    var name: String
    
    init(from string: String) {
        name = string
    }
}

When a class conforms, you must use required init or convenience init:

class User: Decodable {
    var name: String
    
    required init(from string: String) {
        name = string
    }
}

Multiple Protocol Conformance

protocol Printable {
    func printInfo()
}

protocol Loggable {
    func log()
}

struct Event: Printable, Loggable {
    func printInfo() { print("Event") }
    func log() { print("Logged event") }
}

Protocol Inheritance

Protocols can inherit from other protocols:

protocol Named {
    var name: String { get }
}

protocol Aged: Named {
    var age: Int { get }
}

struct Person: Aged {
    var name: String
    var age: Int
}

A type conforming to Aged must satisfy both Aged and Named requirements.

Protocol Extensions

Protocol Extensions

Protocol extensions let you provide default implementations for protocol requirements. This is the foundation of protocol-oriented programming in Swift.

Default Implementations

protocol Greetable {
    func greet() -> String
}

extension Greetable {
    func greet() -> String {
        return "Hello!"
    }
}

struct Person: Greetable {
    var name: String
    // greet() is provided by the extension
}

let p = Person(name: "Alice")
p.greet()  // "Hello!" — from extension

Adding Functionality to Protocols

Extensions can add new methods, computed properties, and subscripts that are not part of the original protocol:

protocol Collection {
    var count: Int { get }
    subscript(index: Int) -> String { get }
}

extension Collection {
    func summary() -> String {
        return "Collection with \(count) items"
    }
    
    func firstItem() -> String? {
        guard count > 0 else { return nil }
        return self[0]
    }
}

struct StringArray: Collection {
    var items: [String]
    var count: Int { items.count }
    subscript(index: Int) -> String { items[index] }
}

let arr = StringArray(items: ["a", "b", "c"])
arr.summary()    // "Collection with 3 items"
arr.firstItem()  // Optional("a")

Protocol Inheritance with Extensions

protocol Stackable {
    associatedtype Element
    mutating func push(_ element: Element)
    mutating func pop() -> Element?
}

extension Stackable {
    var isEmpty: Bool {
        // This is a simplified example
        // Real implementation would check internal state
        return false
    }
}

Specializing Protocol Extensions

You can specialize extensions for types that conform to additional protocols:

protocol Describable {
    var description: String { get }
}

extension Describable {
    var description: String { return "Default" }
}

// Specialized extension for Equatable types
extension Describable where Self: Equatable {
    var description: String {
        return "Equatable: \(self)"
    }
}

Protocol Extensions vs Protocol Requirements

An important subtlety: methods defined in the protocol itself are dispatched dynamically (through the protocol witness table), while methods defined only in the extension are dispatched statically:

protocol Animal {
    func sound() -> String  // protocol requirement
}

extension Animal {
    func sound() -> String { return "..." }  // default impl
    func breathe() -> String { return "breathing" }  // extension only
}

struct Dog: Animal {
    func sound() -> String { return "Woof" }
}

let animal: Animal = Dog()
animal.sound()   // "Woof" — dynamic dispatch
animal.breathe() // "breathing" — static dispatch from extension

This distinction matters when overriding default implementations in subclasses.

Protocol Composition

Protocol Composition

Protocol composition lets you combine multiple protocol requirements into a single constraint using the & operator.

Basic Composition

func printInfo(_ item: Identifiable & Describable) {
    print("ID: \(item.id), Desc: \(item.description)")
}

struct User: Identifiable, Describable {
    let id: String
    var description: String { "User \(id)" }
}

printInfo(User(id: "123"))  // "ID: 123, Desc: User 123"

Type Aliases for Composition

When composing many protocols, create a type alias for clarity:

typealias Presentable = Identifiable & Describable & Printable

func display(_ item: Presentable) {
    print(item.description)
    item.printInfo()
}

Composition with Classes

You can compose a class with protocols:

func configure(_ view: UIView & Configurable & Animatable) {
    view.configure()
    view.animate()
}

Where Clauses

You can constrain associated types using where clauses:

protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func push(_ item: Item)
}

extension Container where Item: Equatable {
    func contains(_ item: Item) -> Bool {
        // simplified
        return false
    }
}

Protocol Existential Types

When you use a protocol as a type, you create an existential type:

let items: [Describable] = [
    Person(name: "Alice"),
    Product(name: "Phone")
]

for item in items {
    print(item.description)
}

In Swift 5.7+, use any to clarify existential types:

let items: [any Describable] = [...]

Opaque Return Types

some Protocol hides the concrete type while guaranteeing it conforms to the protocol:

func makeCounter() -> some Countable {
    return Counter()
}

This preserves type information while abstracting implementation details.

Real-World Example

protocol Cacheable {
    associatedtype Key: Hashable
    associatedtype Value
    func get(_ key: Key) -> Value?
    mutating func set(_ key: Key, value: Value)
}

struct MemoryCache<K: Hashable, V>: Cacheable {
    typealias Key = K
    typealias Value = V
    private var storage: [K: V] = [:]
    
    func get(_ key: K) -> V? { storage[key] }
    mutating func set(_ key: K, value: V) { storage[key] = value }
}

extension Cacheable where V: Codable {
    func persist(to url: URL) throws {
        // save to disk
    }
}

Protocol composition and extensions enable flexible, testable architectures. By depending on protocols rather than concrete types, you achieve loose coupling and easier unit testing.

Quiz

1. What does a protocol define?

Question 1 options

2. What does `&` do in a type constraint?

Question 2 options

3. What is the difference between a protocol requirement and a protocol extension method?

Question 3 options

4. What does `some Protocol` return?

Question 4 options

5. Can structs adopt protocols?

Question 5 options

Flashcards

Question

What is a protocol in Swift?

Answer

A blueprint defining requirements (properties, methods) that types can adopt to conform.

Question

What does `mutating` mean in a protocol method?

Answer

The method can modify the adopting type's properties (needed for structs and enums).

Question

What is protocol composition?

Answer

Combining multiple protocols with `&` to create a single type constraint.

Question

What is an existential type?

Answer

Using a protocol as a type (`let x: Protocol` or `any Protocol`), which can hold any conforming instance.

Question

What is the advantage of protocol-oriented programming?

Answer

Loose coupling, testability, value type support, and avoiding inheritance hierarchies.

Revision Notes

Key Takeaways

  • 1. Protocols define blueprints; types adopt them with conformance
  • 2. Protocol extensions provide default implementations
  • 3. Composition (`&`) combines protocol constraints
  • 4. Protocol requirements use dynamic dispatch; extensions use static
  • 5. Prefer protocols over class inheritance for flexibility

Interview Tips

  • Explain protocol-oriented programming vs object-oriented programming
  • Know the difference between existential and opaque types
  • Be able to create protocol extensions with default implementations
  • Understand when to use `where` clauses on protocol extensions

Cheat Sheet

Protocols & Extensions Cheat Sheet

Defining:

  • protocol Name { } — defines requirements
  • Properties: { get } or { get set }
  • Methods: func method() or mutating func method()

Adopting:

  • struct Type: Protocol1, Protocol2 { }
  • Must implement all protocol requirements

Extensions:

  • Provide default implementations: extension Protocol { }
  • Add new methods not in the protocol
  • Specialize with where clauses

Composition:

  • A & B — combines two protocol constraints
  • Type alias: typealias Combined = A & B

Existentials:

  • let x: Protocol or let x: any Protocol
  • some Protocol — opaque return type