Skip to content
All essays
iOSMarch 27, 202513 min

Swift Protocols and Extensions: Advanced Patterns

Master Swift protocols and extensions. Protocol-oriented programming and powerful code reuse patterns.

Ü
Ümit Uz
Mobile & Full Stack Developer

Protocols and extensions are powerful Swift features that enable protocol-oriented programming and code reuse. Master these patterns for cleaner, more maintainable code.

Protocols

Basic Protocol

swift
protocol Drawable {
    func draw()
    var color: String { get }
}

struct Circle: Drawable {
    let color: String = "blue"

    func draw() {
        print("Drawing a \(color) circle")
    }
}

struct Square: Drawable {
    let color: String = "red"

    func draw() {
        print("Drawing a \(color) square")
    }
}

let shapes: [Drawable] = [Circle(), Square()]
for shape in shapes {
    shape.draw()
}

Protocol with Associated Types

swift
protocol Container {
    associatedtype Item
    mutating func add(_ item: Item)
    var count: Int { get }
    func get(_ index: Int) -> Item
}

struct Stack: Container {
    typealias Item = Int
    private var items: [Item] = []

    mutating func add(_ item: Item) {
        items.append(item)
    }

    var count: Int {
        items.count
    }

    func get(_ index: Int) -> Item {
        items[index]
    }
}

Protocol Inheritance

swift
protocol Vehicle {
    var name: String { get }
    func start()
}

protocol ElectricVehicle: Vehicle {
    var batteryLevel: Int { get }
    func charge()
}

struct Tesla: ElectricVehicle {
    let name: String
    var batteryLevel: Int = 100

    func start() {
        print("\(name) starting...")
    }

    func charge() {
        print("Charging...")
        batteryLevel = 100
    }
}

Extensions

Basic Extension

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

    var isOdd: Bool {
        !isEven
    }

    func squared() -> Int {
        self * self
    }
}

let number = 5
print(number.isEven) // false
print(number.isOdd) // true
print(number.squared()) // 25

Extension with Initializers

swift
struct Temperature {
    let celsius: Double

    init(celsius: Double) {
        self.celsius = celsius
    }
}

extension Temperature {
    init(fahrenheit: Double) {
        self.celsius = (fahrenheit - 32) * 5/9
    }

    init(kelvin: Double) {
        self.celsius = kelvin - 273.15
    }

    var fahrenheit: Double {
        celsius * 9/5 + 32
    }

    var kelvin: Double {
        celsius + 273.15
    }
}

let temp1 = Temperature(celsius: 25)
let temp2 = Temperature(fahrenheit: 77)
let temp3 = Temperature(kelvin: 298.15)

Protocol-Oriented Programming

Protocol Extensions

swift
protocol Summable {
    func sum() -> Int
}

extension Array: Summable where Element == Int {
    func sum() -> Int {
        reduce(0, +)
    }
}

let numbers = [1, 2, 3, 4, 5]
print(numbers.sum()) // 15

Default Implementations

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

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

struct Person: Describable {
    let name: String
}

struct Car: Describable {
    let name: String

    func describe() -> String {
        return "This is a \(name) car"
    }
}

let person = Person(name: "John")
let car = Car(name: "Tesla")

print(person.describe()) // "This is John"
print(car.describe()) // "This is a Tesla car"

Powerful Patterns

Equatable Implementation

swift
struct User {
    let id: String
    let name: String
    let email: String
}

extension User: Equatable {
    static func == (lhs: User, rhs: User) -> Bool {
        lhs.id == rhs.id
    }
}

Comparable Implementation

swift
struct Task {
    let title: String
    let priority: Int
    let completed: Bool
}

extension Task: Comparable {
    static func < (lhs: Task, rhs: Task) -> Bool {
        if lhs.priority != rhs.priority {
            return lhs.priority < rhs.priority
        }
        return lhs.title < rhs.title
    }
}

let tasks = [
    Task(title: "Low", priority: 3, completed: false),
    Task(title: "High", priority: 1, completed: false),
    Task(title: "Medium", priority: 2, completed: false)
]

let sortedTasks = tasks.sorted()

Hashable for Custom Types

swift
struct UserProfile {
    let id: String
    let username: String
    let email: String
}

extension UserProfile: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    static func == (lhs: UserProfile, rhs: UserProfile) -> Bool {
        lhs.id == rhs.id
    }
}

let userProfiles: Set<UserProfile> = [
    UserProfile(id: "1", username: "user1", email: "user1@example.com"),
    UserProfile(id: "2", username: "user2", email: "user2@example.com")
]

Conditional Conformance

swift
struct Wrapper<T> {
    let value: T
}

extension Wrapper: Equatable where T: Equatable {
    static func == (lhs: Wrapper, rhs: Wrapper) -> Bool {
        lhs.value == rhs.value
    }
}

extension Wrapper: Hashable where T: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(value)
    }
}

let wrapper1 = Wrapper(value: "hello")
let wrapper2 = Wrapper(value: "hello")
print(wrapper1 == wrapper2) // true

Opaque Types

swift
protocol Shape {
    func area() -> Double
}

struct Rectangle: Shape {
    let width: Double
    let height: Double

    func area() -> Double {
        width * height
    }
}

struct Circle: Shape {
    let radius: Double

    func area() -> Double {
        .pi * radius * radius
    }
}

struct ShapeFactory {
    func makeShape() -> some Shape {
        Rectangle(width: 10, height: 5)
    }
}

let factory = ShapeFactory()
let shape = factory.makeShape()
print(shape.area())

Extension on Standard Library

String Extensions

swift
extension String {
    var isValidEmail: Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let predicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return predicate.evaluate(with: self)
    }

    var isNumeric: Bool {
        Double(self) != nil
    }

    func truncate(_ length: Int, trailing: String = "...") -> String {
        if self.count <= length {
            return self
        }
        return String(self.prefix(length)) + trailing
    }
}

let email = "user@example.com"
print(email.isValidEmail) // true

Date Extensions

swift
extension Date {
    var formattedDate: String {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .none
        return formatter.string(from: self)
    }

    var isToday: Bool {
        Calendar.current.isDateInToday(self)
    }

    var isYesterday: Bool {
        Calendar.current.isDateInYesterday(self)
    }

    func adding(days: Int) -> Date {
        Calendar.current.date(byAdding: .day, value: days, to: self) ?? self
    }
}

let today = Date()
print(today.formattedDate)
print(today.isToday)

Best Practices

  1. 1Protocol-oriented: Use protocols to define contracts
  2. 2Default implementations: Provide sensible defaults
  3. 3Conditional conformance: Add conformances conditionally
  4. 4Extensions: Extend types, don't subclass
  5. 5Opaque types: Hide implementation details
  6. 6Hashable: Implement for custom types
  7. 7Equatable: Custom equality when needed
  8. 8Codable: Make types serializable

Protocols and extensions enable flexible, reusable Swift code!

Related essays

Next essay
SwiftUI Animations Advanced: Create Engaging Experiences