LEARN SWIFT

Buy the PDF

18.1 try, catch and throws

Swift 2 introduced an error handling mechanism based around ErrorType that is somewhat similar to exceptions in languages like java or ruby.

In Swift an exception is an enumeration that extends ErrorType. We can raise an exception using throw. We can catch an exception using do..catch. We use try to call methods that can throw an exception. An example will make this clearer.

Similar to the older approach we describe at the end of this chapter, an error is an enum which extends type ErrorType. We define fields on the enum to represent the different error states.

enum InvalidNumberError : ErrorType{
    case TooBig
    case Even
}

The throws keyword indicates that a function can throw an exception. Any function that can throw an exception must be declared using throws.

We use the throw keyword to actually raise an exception.

func printSmallNumbers(x:Int) throws {
    guard x % 2 != 0 else {
        throw InvalidNumberError.Even
    }
    guard x < 100 else {
        throw InvalidNumberError.TooBig
    }
    
    print("Numer is \(x)")
}

This method can throw an error since it is declared using throws. Within the method we use the throw keyword when we want to throw an error. This function raises InvalidNumberError.Even for numbers that are even and InvalidNumberError.TooBig for numbers that are bigger than 100.

To call a function that can throw an exception we need to use the try keyword.

try printSmallNumbers(7)

For example, this code will print the number 7.

To catch the exception that are raised and handle them we can use a do..catch block.

do {
    try printSmallNumbers(445)
} catch InvalidNumberError.Even {
    print("Error: number is even")
} catch InvalidNumberError.TooBig {
    print("Error: number is too big")
}

We can use try? to call a function that can raise an exception and turn store the result in an optional.

18.2 Assertions

Assertions let us do runtime checks for debugging. We can assert something is true, and if it isn’t then the program halts with an error.

In this example we have a function that takes a String as an argument and we want to assert that the string isn’t empty.

func printWord(word: String){
    assert(word.characters.count > 1)
    print(word)
}

printWord("abc")
printWord("") // Error

Our assertion is something that evaluates to true or false. We can also pass a message to our assertion as a second argument - that message will be printed out if the assertion fails.

assert(true, "True is true!")
assert(false, "False can't be true!") // Error with message

It’s important to note that assertions are for development and debugging. They provide a way to check the value of variables during development in cases where it’s possible for a variable to have invalid values. Assertions are disabled when your code is compiled with optimisations enabled - i.e. your assertions will be ignored in your production code. (footnote: If you want to do these kind of checks in your production code, a guard statement would be more appropriate).

18.3 Enums (The old way)

When Swift was first released there was no exception handling mechanism. Back then a common way to handle errors was to pattern match the return value of a function against an enum. It’s worth outlining it here as you may still see this technique in older code and blog posts.

enum Result<T> {
    case Success(T)
    case Failure(String)
}

let result = divider(10, by:3)

switch result {
case .Success(let r):
    print("Result is \(r)")
case .Failure(let msg):
    print("Error: \(msg)")
}