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)

\n\n

Error handling is one of the key topics in Swift development. If you are already familiar with basic do-catch and try?, it's time to move to the next level. In this article, we will cover advanced techniques: working with the Error protocol, the Result type, the rethrows keyword, and how to properly design error hierarchies in real iOS/macOS projects.

\n\n

Deep Dive into the Error Protocol and Custom Errors

\n\n

In Swift, any error must conform to the Error protocol. However, to make code more readable and maintainable, it's worth creating custom enumerations (enum) with nested cases and associated values.

\n\n
enum NetworkError: Error {\n    case invalidURL\n    case noData\n    case decodingFailed(description: String)\n    case serverError(statusCode: Int)\n    case timeout(seconds: Double)\n}\n\n// Usage example\nfunc fetchUser(id: Int) throws -> User {\n    guard id > 0 else {\n        throw NetworkError.invalidURL\n    }\n    // ... request logic\n    if someCondition {\n        throw NetworkError.serverError(statusCode: 500)\n    }\n    return User()\n}
\n\n

This approach allows passing additional context: error code, description, or timeout. This is especially useful for logging and debugging.

\n\n

Result: An Alternative to do-catch for Asynchronous Code

\n\n

The Result<Success, Failure> type is a powerful tool that comes from functional programming. It is ideal for callback-based APIs, network requests, and asynchronous operations.

\n\n
enum DataError: Error {\n    case corrupted\n    case notFound\n}\n\nfunc loadImage(from url: URL, completion: @escaping (Result<UIImage, DataError>) -> Void) {\n    DispatchQueue.global().async {\n        // Simulate loading\n        if arc4random_uniform(2) == 0 {\n            completion(.success(UIImage()))\n        } else {\n            completion(.failure(.notFound))\n        }\n    }\n}\n\n// Usage\nloadImage(from: someURL) { result in\n    switch result {\n    case .success(let image):\n        print("Image loaded: \\(image)")\n    case .failure(let error):\n        print("Error: \\(error.localizedDescription)")\n    }\n}
\n\n

Result makes code more predictable and eliminates the need to handle optional values with nil. Additionally, you can use .map and .flatMap for transformation chains.

\n\n

Rethrows: When a Function "Passes Through" Errors

\n\n

The rethrows keyword is one of the most underrated features in Swift. It allows you to write a function that does not throw errors itself but "propagates" them if the passed closure is throws.

\n\n
func performOperation<T>(on array: [T], operation: (T) throws -> Bool) rethrows -> [T] {\n    var result: [T] = []\n    for element in array {\n        if try operation(element) {\n            result.append(element)\n        }\n    }\n    return result\n}\n\n// Without errors (no try needed)\nlet numbers = [1, 2, 3, 4, 5]\nlet evenNumbers = performOperation(on: numbers) { $0.isMultiple(of: 2) }\nprint(evenNumbers) // [2, 4]\n\n// With error (try needed)\nenum ValidationError: Error { case negative }\nlet filtered = try performOperation(on: numbers) { value in\n    guard value > 0 else { throw ValidationError.negative }\n    return value % 2 == 0\n}
\n\n

rethrows makes the API flexible: the user can pass either a regular closure (without try) or a closure with throws (then try will be required). This is often used in the Swift standard library — for example, in map, filter, and for

Blogs

Book Recommendations