Skip to content
AgileLogics
5 min read

Extensions

Prerequisites: Protocols

An extension adds new functionality to an existing type — including types you didn't write, such as Int or String.

Adding to built-in types

extension Int {
    var isEven: Bool { self % 2 == 0 }

    func times(_ action: () -> Void) {
        for _ in 0..<self { action() }
    }
}

extension String {
    var initials: String {
        split(separator: " ").compactMap(\.first).map(String.init).joined()
    }
}

print(4.isEven)
3.times { print("hi") }
print("Grace Brewster Hopper".initials)
true
hi
hi
hi
GBH

Organizing your own types

Extensions are widely used to split a type into readable sections — for example, one extension per protocol conformance:

struct Money {
    let cents: Int
}

extension Money: CustomStringConvertible {
    var description: String {
        "$\(cents / 100).\(String(format: "%02d", cents % 100))"
    }
}

(String(format:) comes from Foundation.)

Putting a custom init in an extension also keeps the struct's automatic memberwise initializer.

Protocol extensions — default implementations

Extending a protocol gives every conforming type a method for free:

protocol Describable {
    var name: String { get }
}

extension Describable {
    func describe() -> String { "This is \(name)" }
}

struct Dog: Describable { let name: String }
struct City: Describable { let name: String }

print(Dog(name: "Rex").describe())
print(City(name: "Oslo").describe())
This is Rex
This is Oslo

This combination — protocols plus default implementations — is the heart of protocol-oriented programming, covered in Level 3.

Limits

Extensions can add methods, computed properties, initializers, nested types, and protocol conformances. They can't add stored properties.

Common mistakes

  • Adding very general names to built-in types (extension String { var value ... }). Keep extension names specific, so they don't clash with other code or future Swift versions.