Lesson 83Error Handling

Do-Try-Catch

Handling Thrown Errors

When calling a throwing function, you must handle potential errors using do-try-catch. This is Swift's primary error handling mechanism.

Basic Structure

do {
    let result = try throwingFunction()
} catch {
    print(error)
}

Catch Patterns

Catch All

catch { ... }

Catches any error

Specific Error

catch MyError.case { ... }

Catches one case

With Pattern

catch let e as MyError { ... }

Cast to specific type

With Where

catch Error.case(let x) where x > 0

Conditional catch

The error Variable

In a catch block without a pattern, Swift automatically provides an error constant of type Error.

Order Matters!

Put specific catches before general ones! Swift matches from top to bottom, and the first matching catch wins.

  • Most specific patterns first
  • General catch block last
  • Unreachable catches cause warnings

Multiple try in One Block

You can have multiple try calls in one do block. If any fails, execution jumps to catch immediately.

main.swift
// DO-TRY-CATCH
// Handle errors from throwing functions

// BASIC SYNTAX
enum MathError: Error {
    case divisionByZero
    case negativeRoot
}

func divide(_ a: Int, by b: Int) throws -> Int {
    if b == 0 { throw MathError.divisionByZero }
    return a / b
}

func squareRoot(_ n: Double) throws -> Double {
    if n < 0 { throw MathError.negativeRoot }
    return n.squareRoot()
}

// BASIC DO-TRY-CATCH
do {
    let result = try divide(10, by: 2)
    print("Result: \(result)")  // Result: 5
} catch {
    print("Error: \(error)")
}

// CATCHING SPECIFIC ERRORS
do {
    let result = try divide(10, by: 0)
    print(result)
} catch MathError.divisionByZero {
    print("Cannot divide by zero!")
} catch MathError.negativeRoot {
    print("Cannot take square root of negative!")
} catch {
    print("Unknown error: \(error)")
}

// MULTIPLE TRY STATEMENTS
do {
    let a = try divide(100, by: 5)
    let b = try divide(a, by: 2)
    let c = try squareRoot(Double(b))
    print("Final result: \(c)")
} catch {
    print("Operation failed: \(error)")
}

// CATCH WITH PATTERN MATCHING
enum NetworkError: Error {
    case timeout(seconds: Int)
    case serverError(code: Int)
    case noConnection
}

func fetchData() throws -> String {
    throw NetworkError.serverError(code: 500)
}

do {
    let data = try fetchData()
    print(data)
} catch NetworkError.timeout(let seconds) {
    print("Request timed out after \(seconds) seconds")
} catch NetworkError.serverError(let code) where code >= 500 {
    print("Server error: \(code)")
} catch NetworkError.serverError(let code) where code >= 400 {
    print("Client error: \(code)")
} catch NetworkError.noConnection {
    print("No internet connection")
} catch {
    print("Other error: \(error)")
}

// CATCH MULTIPLE ERROR TYPES
enum FileError: Error {
    case notFound
    case permissionDenied
}

do {
    // Some operation
} catch is NetworkError {
    print("Network-related error")
} catch is FileError {
    print("File-related error")
} catch {
    print("Other error")
}

// NESTED DO-CATCH
func processData() {
    do {
        let data = try fetchData()

        do {
            // Process the data
            let processed = try transform(data)
            print(processed)
        } catch {
            print("Transform failed: \(error)")
        }

    } catch {
        print("Fetch failed: \(error)")
    }
}

func transform(_ data: String) throws -> String {
    return data.uppercased()
}

// PRACTICAL EXAMPLE: API Response
enum APIError: Error {
    case invalidResponse
    case decodingFailed
    case unauthorized
}

struct User: Codable {
    let id: Int
    let name: String
}

func parseUserResponse(json: Data) throws -> User {
    let decoder = JSONDecoder()
    do {
        return try decoder.decode(User.self, from: json)
    } catch DecodingError.keyNotFound(let key, _) {
        print("Missing key: \(key.stringValue)")
        throw APIError.decodingFailed
    } catch DecodingError.typeMismatch(let type, _) {
        print("Type mismatch for: \(type)")
        throw APIError.decodingFailed
    } catch {
        throw APIError.invalidResponse
    }
}

Try It Yourself!

Write a function that parses a date string and use do-try-catch to handle different parsing errors with specific error messages!