Try Catch Throw: Error Handling in Swift with Code Examples

Try catch in Swift combined with throwing errors make it possible to nicely handle any failures in your code. A method can be defined as throwing which basically means that if anything goes wrong, it can throw an error. To catch this error, we need to implement a so-called do-catch statement.

It’s not always required to use a do-catch statement with throwing method. Let’s go over all the cases and cover them in more detail.

Creating a throwing method using the throws keyword

Creating a throwing method is as easy as adding the throws keyword to a method just before the return statement. In this example, we use a method to update the name for a user of a specific user identifier.

func update(name: String, forUserIdentifier userIdentifier: String) {
    // This method is not throwing any errors
}

func update(name: String, forUserIdentifier userIdentifier: String) throws {
    // The throws keyword makes that this method cán throw an error
}

When calling a throwing method you might get the following error:

Call can throw but is not marked with ‘try’

This means that you have to use the try keyword before a piece of code that can throw an error.

try update(name: "Antoine van der Lee", forUserIdentifier: "AEDKM1323")

Throwing initializer in Swift

A great thing is that you can also create a throwing initializer. This especially comes in handy when you want to validate properties for initializing a certain object. For example, you might want to validate a username before creating a User object.

struct User {
    enum ValidationError: Error {
        case emptyName
        case nameToShort(nameLength: Int)
    }

    let name: String

    init(name: String) throws {
        guard !name.isEmpty else {
            throw ValidationError.emptyName
        }
        guard name.count > 2 else {
            throw ValidationError.nameToShort(nameLength: name.count)
        }

        self.name = name
    }
}

let user = try User(name: "Antoine van der Lee")

Swift Try Catch: Handling Errors in Swift with a do-catch statement

To catch a thrown error in Swift we need to use the do-catch statement. The following example uses the earlier defined User instance.

do {
    let user = try User(name: "")
    print("Created user with name \(user.name)")
} catch {
    print("User creation failed with error: \(error)")
}

// Prints: User creation failed with error: emptyName

The emptyName error is thrown as the provided username is empty. The result is that the catch block is called. As you can see we can use a local error property to print out the caught error. The catch block is only called when an error occurs.

Catching a specific type of error

As we can’t specify the error type which will be thrown by a method we have to take into account that different kinds of error types can be thrown. Therefore, you want to catch and handle specific error types in a separate catch statement.

In the following example, we have implemented the name update method. This method can now throw both a user validation error and a database error thrown from the fetchUser method.

func fetchUser(for identifier: String) throws -> User {
    // Fetches the user from the database
}

func update(name: String, forUserIdentifier userIdentifier: String) throws {
    guard !name.isEmpty else {
        throw User.ValidationError.emptyName
    }
    var user = try fetchUser(for: userIdentifier)
    user.update(name)
    user.save()
}

It would be nice to catch the errors in separated blocks to display a different alert if only the name is invalid. There are several ways of doing this:

do {
    try update(name: "Antoine van der Lee", forUserIdentifier: "AEDKM1323")
} catch User.ValidationError.emptyName {
    // Called only when the `User.ValidationError.emptyName` error is thrown
} catch User.ValidationError.nameToShort(let nameLength) where nameLength == 1 {
    // Only when the `nameToShort` error is thrown for an input of 1 character
} catch is User.ValidationError {
    // All `User.ValidationError` types except for the earlier catch `emptyName` error.
} catch {
    // All other errors
}

There are a few things to point out here:

  • The order of catching is important. In this example, we first catch emptyName specific, all other User.ValidationError after that. If we would swap these two, the specific emptyName catch would never be called.
  • where can be used to filter down on error values. In this example, we only like to catch name inputs with a length of 1 character specifically. If you’re not familiar with the “where” keyword, you can check out my blog post Where usage in Swift.
  • Using the is keyword we can catch errors of a specific type.
  • The generic catch closure in the end catches all other errors.

There’s also scenarios in which you’d like to catch two or more specific error types. In this case, you can use lists in your catch statements:

do {
    try update(name: "Antoine van der Lee", forUserIdentifier: "AEDKM1323")
} catch User.ValidationError.emptyName, User.ValidationError.nameToShort {
    // Only called for `emptyName` and `nameToShort`
} catch {
    // All other errors
}

Note here that we took away the nameToShort paramater. This is something you can always do when working with enums if you’re not interested in the associated value.

Using try? with a throwing method

If you’re not interested in catching any thrown errors you can also decide to use try?. The question mark behind the try keyword basically tells that we’re not interested in the possibly thrown error.

let user = try? User(name: "")
print(user?.name) // Prints "nil" if an error occurred upon init.

The value will either be an optional User instance or nil and the thrown error is completely ignored.

Using try! with a throwing method

If you want your app to fail instantly you can use try! with the throwing method. This will basically fail your app just like a fatal error statement.

let user = try! User(name: "")
print(user.name)

This will end up with the following error as the name input is empty:

Fatal error: ‘try!’ expression unexpectedly raised an error: User.ValidationError.emptyName

Conclusion

Error handling in Swift is great. It allows you to write readable code while also handling the non-happy flow. By being able to catch specific error types or using the where keyword we have the flexibility to handle specific scenarios if needed. Be a good citizen and make your methods throw whenever it can fail!

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!