Swift Error Handling

Programs encounter problems — a file might not exist, a network call might fail, or a user might enter invalid data. Swift's error handling system gives you a structured way to detect these problems, communicate them clearly, and recover gracefully.

The Vending Machine Analogy


┌──────────────────────────────────────────────────┐
│  Buying a snack from a vending machine           │
│                                                  │
│  Possible failures:                              │
│  ❌ Item out of stock                            │
│  ❌ Not enough coins inserted                    │
│  ❌ Machine out of change                        │
│                                                  │
│  Each failure has a name and a cause.            │
│  The machine reports WHY it failed —             │
│  not just that something went wrong.             │
└──────────────────────────────────────────────────┘

Step 1 – Define Error Types

enum VendingError: Error {
    case outOfStock
    case insufficientFunds(needed: Double)
    case invalidSelection
}

Errors in Swift conform to the Error protocol. Using an enum gives every error a specific, descriptive name. Associated values let you attach extra information, like the amount still needed.

Step 2 – Throwing Functions

func buySnack(item: String, paid: Double) throws -> String {
    let price = 25.0
    guard item == "Chips" else {
        throw VendingError.invalidSelection
    }
    guard paid >= price else {
        throw VendingError.insufficientFunds(needed: price - paid)
    }
    return "Here is your \(item)!"
}

The throws keyword in the function signature signals that this function might fail. The throw keyword sends an error to the caller instead of returning a normal value.

Step 3 – Catching Errors with do-try-catch

do {
    let result = try buySnack(item: "Chips", paid: 10.0)
    print(result)
} catch VendingError.insufficientFunds(let needed) {
    print("Insert ₹\(needed) more.")
} catch VendingError.invalidSelection {
    print("Item not available.")
} catch VendingError.outOfStock {
    print("Item is sold out.")
} catch {
    print("Unexpected error: \(error)")
}
// Insert ₹15.0 more.

Error Flow Diagram


┌──────────────────────────────────────────────────────┐
│  try buySnack(item: "Chips", paid: 10.0)             │
│          │                                           │
│     paid < price?                                    │
│          │                                           │
│         YES → throw insufficientFunds(15.0)          │
│          │                                           │
│     catch VendingError.insufficientFunds(let n)      │
│          │                                           │
│     print("Insert ₹15.0 more.")                      │
└──────────────────────────────────────────────────────┘

try? – Convert Error to Optional

let result = try? buySnack(item: "Soda", paid: 30.0)
print(result ?? "Purchase failed")   // Purchase failed

try? calls a throwing function and returns nil if it throws. Use it when you do not need to know which error occurred — just whether it succeeded.

try! – Force Try (Avoid in Production)

let result = try! buySnack(item: "Chips", paid: 50.0)
print(result)   // Here is your Chips!

try! crashes the app if an error is thrown. Only use it when you are absolutely certain the function will not fail — typically in tests or playground experiments.

Rethrowing Functions

func performAction(_ action: () throws -> Void) rethrows {
    try action()
}

try performAction {
    throw VendingError.outOfStock
}

A rethrows function only throws when the closure it receives also throws. If the closure is non-throwing, the function behaves as non-throwing too.

defer – Always Run Cleanup Code

func readFile(name: String) throws {
    print("Opening file")
    defer {
        print("Closing file")   // runs no matter what
    }
    guard name == "data.txt" else {
        throw VendingError.invalidSelection
    }
    print("Reading file contents")
}

try? readFile(name: "other.txt")
// Opening file
// Closing file

defer schedules code to run when the current scope exits — whether it exits normally or through an error. It is perfect for cleanup tasks like closing files or releasing resources.

Leave a Comment

Your email address will not be published. Required fields are marked *