Lesson 81Error Handling

Error Types

Welcome to Error Handling!

Error handling is crucial for building robust applications. Swift provides a powerful, type-safe error handling system that helps you write code that gracefully handles failures.

The Error Protocol

Any type that conforms to the Error protocol can be used as an error. Most commonly, we use enums with associated values to define clear, descriptive errors.

Ways to Define Errors

Enum (Most Common)

Cases represent different error types

Associated Values

Attach context to each error case

Raw Values

Map errors to codes (Int, String)

Struct/Class

Complex errors with multiple properties

LocalizedError Protocol

Conform to LocalizedError to provide user-friendly error messages:

  • errorDescription - Main error message
  • failureReason - Why the error occurred
  • recoverySuggestion - How to fix it

Best Practices

  • Use enums for most error definitions
  • Group related errors in the same enum
  • Use associated values for context
  • Implement LocalizedError for user-facing errors
  • Name errors descriptively (e.g., NetworkError, ValidationError)
main.swift
// ERROR TYPES IN SWIFT
// Swift uses the Error protocol to represent errors

// DEFINING CUSTOM ERRORS WITH ENUM
enum NetworkError: Error {
    case noConnection
    case timeout
    case serverError(code: Int)
    case invalidURL
    case unauthorized
}

enum FileError: Error {
    case notFound
    case permissionDenied
    case corrupted
    case diskFull
}

enum ValidationError: Error {
    case emptyField(fieldName: String)
    case invalidEmail
    case passwordTooShort(minimum: Int)
    case passwordMismatch
}

// ERRORS WITH ASSOCIATED VALUES
enum APIError: Error {
    case badRequest(message: String)
    case notFound(resource: String)
    case serverError(statusCode: Int, body: String)
    case decodingFailed(type: String)
}

// EXAMPLE: Using associated values
let error = APIError.serverError(statusCode: 500, body: "Internal error")

// ERRORS WITH RAW VALUES
enum HTTPError: Int, Error {
    case badRequest = 400
    case unauthorized = 401
    case forbidden = 403
    case notFound = 404
    case serverError = 500
}

let httpError = HTTPError.notFound
print("Status code: \(httpError.rawValue)")  // 404

// CONFORMING TO LocalizedError
enum LoginError: Error, LocalizedError {
    case invalidCredentials
    case accountLocked
    case networkUnavailable

    var errorDescription: String? {
        switch self {
        case .invalidCredentials:
            return "Invalid username or password"
        case .accountLocked:
            return "Your account has been locked"
        case .networkUnavailable:
            return "No internet connection"
        }
    }

    var recoverySuggestion: String? {
        switch self {
        case .invalidCredentials:
            return "Please check your credentials and try again"
        case .accountLocked:
            return "Contact support to unlock your account"
        case .networkUnavailable:
            return "Check your network settings"
        }
    }
}

// Using LocalizedError
let loginError: LoginError = .invalidCredentials
print(loginError.localizedDescription)
// "Invalid username or password"

// STRUCT-BASED ERRORS
struct CustomError: Error {
    let code: Int
    let message: String
    let timestamp: Date

    init(code: Int, message: String) {
        self.code = code
        self.message = message
        self.timestamp = Date()
    }
}

let customErr = CustomError(code: 100, message: "Something went wrong")

// ERROR PROTOCOL REQUIREMENTS
// The Error protocol is empty - any type can conform!
// protocol Error { }

// COMPARING ERRORS
enum AppError: Error, Equatable {
    case unknown
    case network
    case database
}

let err1 = AppError.network
let err2 = AppError.network
print(err1 == err2)  // true

Try It Yourself!

Create a PaymentError enum with cases for insufficientFunds, cardDeclined, and expiredCard. Add associated values and conform to LocalizedError!