Skip to content

Protocols and Extensions

Protocols define contracts for types, enabling polymorphism, while extensions add functionality to existing types. Together, they form the backbone of Swift’s protocol-oriented programming.

Protocols

Protocols specify requirements (properties, methods, initializers) that conforming types must implement.

Defining a Protocol

Example:

swift
protocol Vehicle {
    var speed: Double { get }
    var id: String { get set }
    func accelerate(by: Double)
    init(id: String)
}

struct Bicycle: Vehicle {
    var speed: Double = 0.0
    var id: String
    
    func accelerate(by: Double) {
        speed += max(0, by)
    }
    
    init(id: String) {
        self.id = id
    }
}

Protocol Inheritance

Protocols can inherit multiple protocols.

Example:

swift
protocol Electric {
    var batteryLevel: Double { get }
}

protocol ElectricVehicle: Vehicle, Electric {
    func charge()
}

class Tesla: ElectricVehicle {
    var speed: Double = 0.0
    var id: String
    var batteryLevel: Double = 100.0
    
    func accelerate(by: Double) {
        speed += by
        batteryLevel -= by / 10
    }
    
    func charge() {
        batteryLevel = 100.0
    }
    
    init(id: String) {
        self.id = id
    }
}

Associated Types

Allow protocols to work with generic types.

Example:

swift
protocol Container {
    associatedtype Item
    var items: [Item] { get }
    mutating func add(_ item: Item)
}

struct Stack<T>: Container {
    var items: [T] = []
    mutating func add(_ item: T) {
        items.append(item)
    }
}

Protocol Composition

Combine multiple protocols using &.

Example:

swift
protocol Named {
    var name: String { get }
}

func describe(_ item: Named & Vehicle) {
    print("\(item.name) with speed \(item.speed)")
}

Optional Requirements

Use @objc and optional for optional protocol members (Objective-C interop).

Example:

swift
@objc protocol Delegate {
    @objc optional func didFinish()
}

Extensions

Extensions add methods, properties, initializers, or protocol conformances to types.

Basic Extension

Example:

swift
extension Int {
    var squared: Int {
        return self * self
    }
    func isEven() -> Bool {
        return self % 2 == 0
    }
}

print(5.squared) // 25
print(4.isEven()) // true

Protocol Conformance via Extension

Add protocol conformance to existing types.

Example:

swift
protocol Describable {
    var description: String { get }
}

extension String: Describable {
    var description: String {
        return "\"\(self)\""
    }
}

print("Hello".description) // "Hello"

Extension with Constraints

Limit extensions to specific conditions.

Example:

swift
extension Collection where Element: Equatable {
    func countOccurrences(of item: Element) -> Int {
        return self.filter { $0 == item }.count
    }
}

let array = [1, 2, 2, 3]
print(array.countOccurrences(of: 2)) // 2

Protocol Extensions

Provide default implementations for protocol requirements.

Example:

swift
protocol Loggable {
    func log()
}

extension Loggable {
    func log() {
        print("Logging \(Self.self)")
    }
}

struct User: Loggable {}
User().log() // "Logging User"

Conditional Protocol Extensions

Apply defaults based on constraints.

Example:

swift
extension Loggable where Self: Describable {
    func log() {
        print("Logging \(description)")
    }
}

struct Item: Loggable, Describable {
    var description: String { "Item" }
}
Item().log() // "Logging Item"

Protocol-Oriented Programming (POP)

Swift encourages POP, where protocols and extensions define behavior over inheritance.

Example:

swift
protocol Renderable {
    func render()
}

extension Renderable {
    func render() {
        print("Rendering \(Self.self)")
    }
}

struct Shape: Renderable {}
struct Text: Renderable {}

let renderables: [Renderable] = [Shape(), Text()]
renderables.forEach { $0.render() }
// Output: Rendering Shape
// Rendering Text

Best Practices

  • Protocol Granularity: Define small, focused protocols.
  • Default Implementations: Use extensions to reduce boilerplate.
  • Associated Types: Use for flexible, generic protocols.
  • Composition: Prefer protocol composition over deep inheritance.
  • Avoid Optional Requirements: Use Swift-native protocols when possible.
  • Extension Organization: Group related extensions in separate files.
  • POP over OOP: Favor protocols for flexibility and testability.

Troubleshooting

  • Protocol Conformance Errors: Ensure all requirements are implemented.
  • Associated Type Ambiguity: Use type annotations or constraints.
  • Extension Conflicts: Resolve with more specific constraints.
  • Optional Protocol Issues: Verify @objc for Objective-C compatibility.
  • Performance: Avoid excessive protocol dispatching in critical paths.

Example: Comprehensive Protocol and Extension Usage

swift
protocol Identifiable {
    associatedtype ID: Hashable
    var id: ID { get }
}

protocol Storable {
    func save()
}

protocol Manageable: Identifiable, Storable {
    func delete()
}

extension Manageable {
    func delete() {
        print("Deleting item with ID: id\(id)")
    }
}

struct Product: Manageable {
    typealias ID = String
    let id: String
    let name: String
    
    func save() {
        print("Saving product \(name) with id: id\(id)")
    }
}

extension Collection where Element: Manageable {
    func saveAll() {
        forEach { $0 }.save()
    }
    func deleteAll() {
        forEach { $0 }.delete()
    }
}

let products = [
    Product(id: "P1", name: "Laptop"),
    Product(id: "P2", name: "Phone")
]
products.saveAll()
// Output:
// Saving product Laptop with id: idP1
// Saving product Phone with id: idP2
products.deleteAll()
// Output:
// Deleting item with id: idP1
// Deleting item with id: idP2

Released under the MIT License.