Try catch throw: implementing Error Handling in Swift

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.

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
    }

    let name: String

    init(name: String) throws {
        guard !name.isEmpty else {
            throw ValidationError.emptyName
        }
        self.name = name
    }
}

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

Catch a thrown error

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 kind of error types can be thrown. Therefore, sometimes, you want to catch and handle specific error types in a separated 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.

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.

do {
    try update(name: "Antoine van der Lee", forUserIdentifier: "AEDKM1323")
} catch User.ValidationError.emptyName {
    // Show alert: "You've filled in an empty username"
} catch {
    // Show alert: "Couldn't find a user for that identifier"
}

It’s as simple as defining the error type and case after the catch statement.

Combining catch with where

Sometimes you want to only handle errors based on specific conditions which we can do with the where keyword. If you’re not familiar with this keyword, you can check out my blog post Where usage in Swift.

In this example, we introduce a shouldShowAlertOnFailure property and use that in our catch statement.

var shouldShowAlertOnFailure = false

do {
    try update(name: "Antoine van der Lee", forUserIdentifier: "AEDKM1323")
} catch User.ValidationError.emptyName where shouldShowAlertOnFailure == true {
    // Show alert: "You've filled in an empty username"
} catch {
    guard shouldShowAlertOnFailure else { return }

    // Show alert: "Couldn't find a user for that identifier"
}

Do note that the last catch statement can’t be combined with the where keyword as we would otherwise end up with a do statement which will never be caught. Therefore, we use the guard statement to exit early.

Using try? with a throwing method

If you’re not interested in catching any thrown errors you can also decide to use try?.

let user = try? User(name: "")
print(user?.name) // Prints "nil" as the username is empty and the user instance could, therefore, not be created.

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 scenario’s if needed. Check out the Apple documentation if you want to read more on error handling in Swift. Be a good citizen and make your methods throw whenever it can fail!

Thanks!