Swift Error Handling

Swift Error Handling

Error handling is the process of responding to and recovering from error conditions in your program.

In modern software, things don't always go as planned. Network connections fail, files go missing, and invalid data is entered.

Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at runtime.

This system ensures your code is robust, predictable, and incredibly safe.


Representing Errors

In Swift, errors are represented by values of types that conform to the Error protocol.

This empty protocol indicates that a type can be used for error handling.

Swift enumerations (enum) are particularly well suited for modeling a group of related error conditions.

Defining Custom Errors:

enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

print("Custom error type defined successfully!")

By using an enum, you can even attach associated values to the error to provide extra context (like coinsNeeded).


Throwing Errors

When a function encounters an error condition, it can "throw" an error to alert the caller that something went wrong.

You use the throw keyword to throw an error.

To indicate that a function can throw an error, you must write the throws keyword in its declaration.

Throwing an Error:

enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

func vend(itemNamed name: String) throws { // Simulating an out of stock error if name == "Candy" { throw VendingMachineError.outOfStock } print("Dispensing \(name)") }


Handling Errors with Do-Catch

Once an error is thrown, some surrounding piece of code must be responsible for handling it.

The most common way to handle errors is by using a do-catch statement.

Inside the do block, you write the code that might throw an error, preceded by the try keyword.

Do-Catch Example:

enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

func vend(itemNamed name: String) throws { if name == "Candy" { throw VendingMachineError.outOfStock } print("Dispensing \(name)") }

do { // We use 'try' because this function is marked with 'throws' try vend(itemNamed: "Candy") print("Enjoy your snack!") } catch VendingMachineError.outOfStock { print("Sorry, that item is out of stock.") } catch { print("An unknown error occurred: \(error)") }

If an error is thrown, execution immediately transfers to the corresponding catch block.


Converting Errors to Optionals

Sometimes you don't care about the exact details of the error; you just want to know if an operation succeeded or failed.

You can use try? to handle an error by converting it into an Optional value.

If an error is thrown, the expression evaluates to nil. If it succeeds, it evaluates to an Optional containing the return value.

Using try?:

enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

func vend(itemNamed name: String) throws { if name == "Candy" { throw VendingMachineError.outOfStock } print("Dispensing \(name)") }

let result = try? vend(itemNamed: "Chips") // result is nil if it failed, or Void if it succeeded


Exercise

Which keyword must be used when calling a function that is capable of throwing an error?