Lesson 85Error Handling

Defer Statement

Guaranteed Cleanup

The defer statement executes code just before leaving the current scope, no matter how you leave it - normal return, early return, or thrown error.

Key Properties

Always Executes

Runs on return, throw, or break

LIFO Order

Multiple defers run in reverse order

Scope-Based

Tied to current scope (function, loop, if)

No Control Transfer

Can't return, break, or throw from defer

Common Use Cases

  • File handles - Close files after reading/writing
  • Locks - Release locks after critical sections
  • Database - Close connections after queries
  • Network - Clean up sessions
  • Timing - Measure elapsed time
  • Logging - Log function exit

Multiple Defers

When you have multiple defer statements, they execute in reverse order (Last In, First Out).

Think of it like a stack - last defer declared is first to execute.

Important Note

defer only runs if its line is reached! If you return before the defer statement, it won't execute.

Best practice: Put defer right after resource acquisition.

main.swift
// DEFER STATEMENT
// Execute code when leaving the current scope

// BASIC DEFER
func processFile() {
    print("1. Starting")

    defer {
        print("4. Cleanup (defer)")
    }

    print("2. Processing")
    print("3. Finishing")
}

processFile()
// Output:
// 1. Starting
// 2. Processing
// 3. Finishing
// 4. Cleanup (defer)

// DEFER WITH ERROR HANDLING
enum FileError: Error { case notFound }

func readAndProcess() throws {
    let file = openFile()
    defer {
        closeFile(file)  // Always runs, even if error thrown!
        print("File closed")
    }

    // Process file... might throw
    try processContents(file)
}

func openFile() -> Int { return 1 }
func closeFile(_ f: Int) { }
func processContents(_ f: Int) throws { }

// MULTIPLE DEFERS - Execute in REVERSE order (LIFO)
func multipleDefers() {
    defer { print("First defer") }
    defer { print("Second defer") }
    defer { print("Third defer") }

    print("Function body")
}

multipleDefers()
// Output:
// Function body
// Third defer
// Second defer
// First defer

// DEFER FOR RESOURCE CLEANUP
class DatabaseConnection {
    func open() { print("DB opened") }
    func close() { print("DB closed") }
    func query(_ sql: String) throws -> [String] { return [] }
}

func fetchUsers() throws -> [String] {
    let db = DatabaseConnection()
    db.open()

    defer {
        db.close()  // Guaranteed to close!
    }

    // Multiple operations that might throw
    let users = try db.query("SELECT * FROM users")
    let admins = try db.query("SELECT * FROM admins")

    return users + admins
}

// DEFER WITH LOCKS
import Foundation

class ThreadSafeCounter {
    private var value = 0
    private let lock = NSLock()

    func increment() {
        lock.lock()
        defer {
            lock.unlock()  // Always unlock!
        }

        value += 1
        // Even if something goes wrong, lock is released
    }
}

// DEFER IN LOOPS
func processItems(_ items: [String]) {
    for item in items {
        defer {
            print("Done with \(item)")
        }
        print("Processing \(item)")
    }
}

processItems(["A", "B", "C"])
// Processing A
// Done with A
// Processing B
// Done with B
// Processing C
// Done with C

// DEFER WITH EARLY RETURNS
func validate(input: String?) -> Bool {
    defer {
        print("Validation complete")
    }

    guard let input = input else {
        print("Input is nil")
        return false  // defer still runs!
    }

    guard !input.isEmpty else {
        print("Input is empty")
        return false  // defer still runs!
    }

    return true
}

// DEFER FOR TIMING
func measureTime() {
    let start = Date()

    defer {
        let elapsed = Date().timeIntervalSince(start)
        print("Elapsed: \(elapsed) seconds")
    }

    // Do some work...
    for _ in 0..<1000000 { }
}

// DEFER WITH CONDITIONALS
func conditionalDefer(shouldCleanup: Bool) {
    if shouldCleanup {
        defer {
            print("Cleanup performed")
        }
    }

    print("Main work")
}

// Note: defer only runs if its scope is entered!
conditionalDefer(shouldCleanup: false)  // No cleanup
conditionalDefer(shouldCleanup: true)   // Cleanup performed

// PRACTICAL EXAMPLE: File Handling
func writeToFile(data: String) throws {
    let file = fopen("/tmp/test.txt", "w")
    guard file != nil else {
        throw FileError.notFound
    }

    defer {
        fclose(file)
        print("File handle closed")
    }

    // Write data...
    fputs(data, file)
}

Try It Yourself!

Create a Transaction class that uses defer to automatically rollback if commit isn't called before the scope ends!