BUNDLE: Jumpstart your iOS development with the Swift Power Pack – now available! >>

Error handling in Swift 2: try, catch, do and throw

If you already read my article discussing all the new features in Swift 2 but wanted to know more about the new error handling system, this article is for you. Put simply, it's been entirely rewritten to be modern, fast and safe, and unless you use only a small subset of the iOS APIs you're going to need to learn about it.

If you liked this article, you might also want to read:

How it used to be: NSError and NSErrorPointer

The historical method for handling error is by using an NSError object passed as a pointer. In Objective C this would be NSError*, but in Swift you would see NSError? abd NSErrorPointer.

When you called a method that could fail, you would pass in an empty NSError as a parameter, which would get filled up if there were a problem. This freed up the return value of the method to be the data you actually cared about. For example, loading an NSString from disk looked like this in Swift 1.2:

var err: NSError?
let contents = NSString(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: &err)

if err != nil {
    // uh-oh!
}

This style of programming is pervasive in Cocoa, or at least was: Swift 2 does away with it, and so the above code either needs to be rewritten or removed.

As for why, there are a number of reasons. For example, with the above call it's easy to ignore the error either by not examining the err value or by not even using an NSError and instead passing in nil.

Although Swift 2's new error handling takes a little more work, it is much clearer for programmers to read, it does away with complexities like & to pass in NSErrors, and it gives you greater safety by ensuring you catch all errors.

The Swift 2 approach: try, catch, do and throw

When you import a Swift 1.2 project into Xcode 7 you'll be asked whether you want to convert it to the latest Swift syntax. It won't generate exactly the same code you would have written by hand, but it does a huge amount of work for you so you're almost certainly going to want to use it.

In the case of loading strings from a file using the code above, it will convert it to this Swift 2 equivalent:

let contents: NSString?
do {
    contents = try NSString(contentsOfFile: filePath, encoding: NSUTF8StringEncoding)
} catch _ {
    contents = nil
}

That illustrates three of the five new keywords you'll need to learn. Well, technically one of them is old, but it's usage is new: do was previously used in do … while loops, but to avoid confusion this was renamed to repeat … while in Swift 2.

The fourth and fifth keywords are throw and throws, and we'll look at them in depth now.

Please create a new Xcode project, using the Single View Application template. You can name it whatever you feel like, and target whatever device you want – it doesn't matter, because we're not doing anything visual here.

Select ViewController.swift and add this new method:

func encryptString(str: String, withPassword password: String) -> String {
    // complicated encryption goes here
    let encrypted = password + str + password
    return String(encrypted.characters.reverse())
}

That method is going to encrypt an string using the password that gets sent in. Well, it's not actually going to do that – this article isn't about encryption, so my "encryption" algorithm is pathetic: it puts the password before and after the input string, then reverses it. You're welcome to add the complex encryption algorithm yourself later on!

Modify viewDidLoad() to call that method by adding this:

let encrypted = encryptString("secret information!", withPassword: "12345")
print(encrypted)

When you run your app now, you'll see "54321!noitamrofni terces54321" printed out in the Xcode terminal. Easy, right?

But there's a problem: assuming you actually do put in a meaningful encryption algorithm, there's nothing stopping users from entering an empty string for a password, entering obvious passwords such as "password", or even trying to call the encryption method without any data to encrypt!

Swift 2 comes to the rescue: you can tell Swift that this method can throw an error if it finds itself in an unacceptable state, such as if the password is six or fewer characters. Those errors are defined by you, and Swift goes some way to ensuring you catch them all.

To get started, we need the throws keyword, which you add to your method definition before its return value, like this:

func encryptString(str: String, withPassword password: String) throws -> String {
    // complicated encryption goes here
    let encrypted = password + str + password
    return String(encrypted.characters.reverse())
}

As soon as you do that, your code stops working: adding throws has actually made things worse! But it's worse for a good reason: Swift's try/catch system is designed to be clear to developers, which means you need to mark any methods that can throw using the try keyword, like this:

let encrypted = try encryptString("secret information!", withPassword: "12345")

…but even now your code won't compile, because you haven't told Swift what to do when an error is thrown. This is where the do and catch keywords come in: they start a block of code that might fail, and handle those failures. In our basic example, it might look like this:

do {
    let encrypted = try encryptString("secret information!", withPassword: "12345")
    print(encrypted)
} catch {
    print("Something went wrong!")
}

That silences all the errors, and your code runs again. But it's not actually doing anything interesting yet, because even though we say encryptString() has the potential to throw an error, it never actually does.

How to throw an error in Swift 2

Before you can throw an error, you need to make a list of all the possible errors you want to throw. In our case, we're going to stop people from providing empty passwords, short passwords and obvious passwords, but you can extend it later.

To do this, we need to create an enum that represents our type of error. This needs to build on the built-in ErrorType enum, but otherwise it's easy. Add this before class ViewController:

enum EncryptionError: ErrorType {
    case Empty
    case Short
}

That defines our first two encryption error types, and we can start using them immediately. As these are preconditions to running the method, we're going to use the new guard keyword to make our intentions clear.

Put this at the start of encryptString():

guard password.characters.count > 0 else { throw EncryptionError.Empty }
guard password.characters.count >= 5 else { throw EncryptionError.Short }

If you run the app now nothing will have changed, because we're providing the password "12345". But if you set that to an empty string, you'll see "Something went wrong!" printed in the Xcode console, showing the error.

Of course, having a single error message isn't helpful – there are several ways the method call can fail, and we want to provide something meaningful for each of them. So, modify the try/catch block in viewDidLoad() to this:

do {
    let encrypted = try encryptString("secret information!", withPassword: "")
    print(encrypted)
} catch EncryptionError.Empty {
    print("You must provide a password.")
} catch EncryptionError.Short {
    print("Passwords must be at least five characters, preferably eight or more.")
} catch {
    print("Something went wrong!")
}

Now there are meaningful error messages, so our code is starting to look better. But you may notice that we still need a third catch block in there even though we already caught both the .Empty and .Short cases.

Swift 2 wants exhaustive try/catch error handling

If you recall, I said "Swift goes some way to ensuring you catch them all" and here's where that becomes clear: we're catching both errors we defined, but Swift also wants us to define a generic catch all to handle any other errors that might occur. We don't tell Swift what kind of error our encryption method might throw, just that it throws something, so this extra catch-all block is required.

This does have one downside: if you add any future values to the enum, which we're about to do, it will just drop into the default catch block – you won't be asked to provide any code for it as would happen with a switch/case block.

We're going to add a new value to our enum now, to detect obvious passwords. But we're going to use Swift's super-powerful enums so that we can return a message along with the error type. So, modify the EncryptionError enum to this:

enum EncryptionError: ErrorType {
    case Empty
    case Short
    case Obvious(String)
}

Now when you want to throw an error of type EncryptionError.Obvious you must provide a reason,.

guard password != "12345" else { throw EncryptionError.Obvious("I've got the same passcode on my luggage!") }

Obviously you don't want to provide hundreds (or thousands!) of guard statements to filter out obvious passwords, but hopefully you remember how to use UITextChecker to do spell checking – that would be a smart thing here!

That's our basic do/try/throw/catch Swift example complete. You might look at the try statement and think it useless, but it's primarily there to signal to developers "this call might fail." This matters: when a try calls fails, execution immediately jumps to the catch blocks, so if you see try before a call it signals that the code beneath it might not get called.

There's one more thing to discuss, which is what to do if you know a call simply can't fail, for whatever reason. Now, clearly this is a decision you need to make on a case-by-case basic, but if you know there's absolutely no way a method call might fail, or if it did fail then your code was so fundamentally broken that you might as well crash, you can use try! to signal this to Swift.

When you use the try! keyword, you don't need to have do and catch around your code, because you're promising it won't ever fail. Instead, you can just write this:

let encrypted = try! encryptString("secret information!", withPassword: "12345")
print(encrypted)

Using the try! keyword communicates your intent clearly: you're aware there's the theoretical possibility of the call failing, but you're certain it won't happen in your use case. For example, if you're trying to load the contents of a file in your app's bundle, any failure effectively means your app bundle is damaged or unavailable, so you should terminate.

That's all for error handling in Swift 2. If you'd like to learn about how Swift handles try/finally you should read my article on Swift's defer keyword.

Get six books for only $100

The Swift Power Pack includes my first six books for one low price, helping you jumpstart a new career in iOS development – check it out!

Click here to visit the Hacking with Swift store >>