How to catch different errors in Swift

Updated Jan 11, 2024#swift#errors

Error handling allows you to write robust code that can handle unexpected situations gracefully and prevent your program from crashing. Swift supports error handling with the try, catch, do, throw, and throws keywords.

There’s no way to specify the type of error that throws out because Swift’s error handling system is designed to be flexible and expressive. Any type that conforms to the Error protocol can be used to represent an error condition, and the compiler does not enforce any specific error type for a throwing function. This allows you to use different kinds of errors for different situations, such as enums, structs, classes.

However, this also means that you cannot rely on the compiler to check the error type for you, you have to use runtime checks or type casting to determine what kind of error you are dealing with.

When you use do...catch in Swift to handle errors, you can write a pattern after catch to indicate what errors that clause can handle. A pattern is a way of describing a value or a range of values that can match a certain condition.

do {
    try <#expression#>
    <#statements#>
} catch <#pattern 1#> {
    <#statements#>
} catch <#pattern 2#> where <#condition#> {
    <#statements#>
} catch <#pattern 3#>, <#pattern 4#> where <#condition#> {
    <#statements#>
} catch {
    <#statements#>
}

If a catch clause doesn’t have a pattern, the clause matches any error and binds the error to a local constant named error. This means that you can access the error object in the catch block without specifying its type or name.

There are different ways to catch different errors in Swift, depending on the type and structure of the error:

Catch any errors

If you want to catch any error, you can use a generic catch block without specifying a particular error type. In this case, the catch block will catch any error that is thrown within the corresponding do block, and you can access information about the error through the error variable.

do {
    // code that may throw an error
} catch {
    print("An error occurred: \(error)")
}

Catch errors by case

This is useful when you have an enum that conforms to the Error protocol and defines different cases for different errors.

enum NetworkError: Error {
    case invalidURL
    case noConnection
    case timeout
}

func downloadData(from url: String) throws -> Data {
    // some code that might throw a NetworkError
}

do {
    let data = try downloadData(from: "https://example.com")
    // use data
} catch NetworkError.invalidURL {
    // handle invalid URL error
} catch NetworkError.noConnection {
    // handle no connection error
} catch NetworkError.timeout {
    // handle timeout error
} catch {
    // unexpected error occurred
}

You can also catch by error case with an associated value. This is useful when you have an enum that conforms to the Error protocol and defines different cases with associated values that provide more information about the error.

enum FileError: Error {
    case notFound(path: String)
    case permissionDenied(user: String)
    case outOfSpace(available: Int)
}

func readFile(at path: String) throws -> String {
    // some code that might throw a FileError
}

do {
    let contents = try readFile(at: "/Users/bob/Documents/file.txt")
    // use contents
} catch FileError.notFound(let path) {
    // handle not found error with path
} catch FileError.permissionDenied(let user) {
    // handle permission denied error with user
} catch FileError.outOfSpace(let available) {
    // handle out of space error with available bytes
}

Catch errors by type

This is useful when you want to catch all errors of a specific type and also access the error value for further processing.

Note that the order of the catch blocks matters: you need to put the more specific types before the more general ones, otherwise the more general catch block will catch the error first and the more specific ones will never be executed.

do {
    // Code that may throw an error
} catch let error as MyErrorType {
    // Code that handles MyErrorType
} catch let error as AnotherErrorType {
    // Code that handles AnotherErrorType
} catch {
    // Code that handles any other error
}