Error Handling in Swift: try/catch and Advanced Techniques

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Error Handling in Swift: try/catch and Beyond — Advanced Level



Error handling is an integral part of any serious iOS/macOS development. Swift provides a powerful and expressive error management system that goes far beyond simple try/catch. In this article, we will explore advanced techniques: working with the Error protocol, error enums, the throws and rethrows keywords, as well as the Result type and functional approaches. The material is intended for developers already familiar with the basics of Swift.



1. The Error Protocol and Custom Error Types



At the core of error handling in Swift is the Error protocol. Any type that conforms to this protocol can be thrown as an error. Most often, enums (enum) are used for this purpose.



import Foundation

// Define a custom error type for a network requestenum NetworkError: Error { case invalidURL case noData case decodingFailed(description: String) case serverError(statusCode: Int)}

// A function that can throw an errorfunc fetchUserData(from urlString: String) throws -> Data { guard let url = URL(string: urlString) else { throw NetworkError.invalidURL }

// Simulate a network request let success = Bool.random() if !success { throw NetworkError.serverError(statusCode: 500) }

return Data()}


Using enums allows you to group related errors and pass additional information (associated values), making the code more readable and self-documenting.



2. Advanced Use of do-catch



The do-catch block is the primary tool for catching errors. At an advanced level, it is important to be able to handle different types of errors differently.



func processUserData() {    do {        let data = try fetchUserData(from: "https://api.example.com/user")        // Work with the data        print("Data received: \\(data.count) bytes")    } catch NetworkError.invalidURL {        print("Error: invalid URL")    } catch NetworkError.serverError(let code) where code >= 500 {        print("Server error with code \\(code). Please try again later.")    } catch let error as NetworkError {        print("Network error: \\(error)")    } catch {        // Handle all other errors        print("Unknown error: \\(error.localizedDescription)")    }}


Key points:

  • Pattern matching with where: allows filtering errors by additional conditions (e.g., a range of response codes).
  • Type casting (as): if you expect multiple error types, you can use catch let error as MyErrorType.
  • Generic catch: should always be last, as it catches any unhandled error.


3. The throws, rethrows Keywords and Propagating Errors



The throws keyword indicates that a function can throw an error. rethrows is a more subtle mechanism: it states that a function can throw an error only if one of its closure parameters throws an error.



// Function with throws — can throw an error itselffunc loadConfig() throws -> [String: String] {    // ...    throw NetworkError.noData}

// Function with rethrows — propagates an error from a closurefunc performWithRetry(times: Int, operation: () throws -> Void) rethrows { for _ in 0..<times { try operation() }}

// Usagedo { try performWithRetry(times: 3) { try loadConfig() }} catch { print("Operation failed after 3 attempts: \\(erro

Blogs

Book Recommendations