Lesson 6

Error Handling

Error handling refers to the process of handling error conditions in your app. Swift 2.0 adds new statements that give you the ability to throw, catch, and manipulate runtime errors. Prior to Swift 2.0, if your function wanted to indicate failure, it would do so by returning an Optional variable with a nil value. Errors provide a streamlined solution to the problem of indicating failure within a function and handling the failure.

The ErrorType Protocol

An error can be represented by a class, struct, or enumeration that implements the ErrorType protocol. In most cases, you will use enumerations to represent errors. The following code snippet lists an enumeration called NetworkError that could be used to represent error conditions encountered while making a network request.

enum NetworkError: ErrorType {
    case ResourceNotFound
    case ServerError(httpErrorCode:Int)
    case NetworkTimeout
}

NetworkError could be used to represent three potential scenarios:

  • ResoureNotFound: The URL you were trying to reach couldn't be located.
  • NetworkTimeout: The network request timed out.
  • ServerError: Any other error generated by the server, the HTTP error code will be included as an associated value—httpErrorCode.

Throwing and Catching Errors

To indicate that a function can throw a runtime error, you must add the throws keyword to the end of the function declaration:

func doSomething() throws {
…
}

If your function returns a value, then you must add the throws keyword before the return arrow (->):

func downloadResource(resourceName:String) throws -> NSData?
{
}

If a function is not declared with the throws keyword, it cannot throw a runtime error. To throw an error from a throwing function, you can use the throw keyword:

func downloadResource(resourceName:String) throws -> NSData?
{
    if resourceName.isEmpty
    {
        throw NetworkError.ResourceNotFound
    }
    return nil;
}

When calling a function that can throw, you must write try in front of the function call. This keyword is used to indicate the fact that the function being called may throw an error and the lines of code after the function call may not be executed as a result. The following snippet shows how you would call the downloadResource function.

let homeScreenBanner:NSData? = try downloadResource("homeScreenBanner.png")

Adding a try statement before a function call does not catch or handle any of the errors that can be generated by the function. It simply serves to highlight the fact that the function you are calling can throw one or more errors. To catch and handle errors, you wrap the call to the function in a docatch statement: The general form of the docatch statement is presented next:

do{
    try  A function that throws an error
}
    catch An Error Matching Pattern {
}

If a function throws an error, that error propagates up the call stack until a suitable catch clause is found that can handle the error. If no catch clause is found, then the application is terminated with a runtime error. A catch clause is followed by an optional pattern used to match errors and a bunch of statements that will be executed if the match is a success.

You can have multiple catch clauses in a docatch statement. You can also create a catch-all clause by omitting the pattern. Multiple catch clauses, and a catch-all clause are demonstrated in the following code snippet:

func loadHomeScreenImages() {
    do {
        let homeScreenBanner:NSData? = try downloadResource("homeScreenBanner.png")
    } catch (NetworkError.NetworkTimeout) {
 print("Network error occurred!")
    } catch {
        print("Some other error occurred!")
    }
}

catch clauses must be exhaustive; you need to either provide a catch clause for every exception that can be generated by the throwing function or include a catch-all clause.

You can, however, decide to handle some of the errors and pass the rest higher up the call stack. To do so, simply append the throws keyword to the method that is handling the partial list of errors. For example, the following code snippet will compile fine even though it does not handle every possible error that the downloadResource method can throw:

func loadHomeScreenImages() throws {
    do{
        let homeScreenBanner:NSData? = try downloadResource("homeScreenBanner.png")
    }
    catch (NetworkError.NetworkTimeout)
    {
        print("Network error occured!")
    }
}

This code will compile because loadHomeScreenImages itself is declared as a method that can throw errors (in this case, propagate errors from the downloadResource function).

Suppressing Error Handling

If you are sure that a throwing function will, in fact, not throw an error at runtime, you can opt to suppress error handling by using the forced try expression (try!). The following snippet uses the forced try expression in the call to downloadResource to suppress error handling.

func loadHomeScreenImages() {
    let homeScreenBanner:NSData? = try! downloadResource("homeScreenBanner.png")
    print("\(homeScreenBanner)")
}

If a method that is called with the forced try expression generates a runtime error, then your application will be terminated.

The defer Statement

When a runtime error occurs, code execution usually leaves the current block of code and propagates up the call stack until an appropriate catch expression is found. Often you may want to execute some cleanup code when an error occurs, before code execution leaves the current scope. The defer statement allows you to do just that.

A defer statement delays execution until the current scope is exited:

func downloadResource(resourceName:String) throws -> NSData?
{
    if resourceName.isEmpty
    {
        throw NetworkError.ResourceNotFound
        defer {
            // insert cleanup code here.
        }
    }
    else
    {
        return NSData(contentsOfURL: NSURL(string: resourceName)!)
    }
    return nil;
}

You cannot execute any code in a defer block that would cause execution control to jump out of the block. Therefore, you cannot use the break statement or the return statement, or throw an error.

Try It

In this Try It, you create a new Swift playground and build a function that divides two numbers and throws an exception if the denominator is zero.

Lesson Requirements

  • Launch Xcode.
  • Create a new Swift playground.
  • Create a function called divideNumbers that divides two numbers and throws an exception.
  • Create a function that calls divideNumbers and handles any exceptions that are thrown by divideNumbers.
  • Display the results in the console.

Hints

To view the console inside the playground window, select View arrow Debug Area arrow Activate Console.

Step by Step

  • Create a new Swift playground.
    1. Launch Xcode and create a new Swift playground by selecting File arrow New arrow Playground.
    2. In the playground options screen, use the following values:
      • Name: ExceptionsPlayground
      • Platform: iOS
    3. Save the playground onto your hard disk.
  • Create an enumeration to represent errors.
    1. Delete the default contents of the playground file.
    2. Type the following lines:
      enum ArithmeticError: ErrorType {
          case DivisionByZero
      }
  • Create the divideNumbers function.

    Type the following lines after the definition of the ArithmeticError enumeration:

    func divideNumebrs(numerator n:Double, denominator d:Double) throws -> Double
    {
        if d == 0
        {
            throw ArithmeticError.DivisionByZero
        }
        return n / d
    }
  • Create a function that calls the divideNumbers function and handles any errors that may be generated.

    Type the following lines after the definition of the divideNumbers enumeration:

    func performDivision(number1:Double, _ number2:Double)
    {
        do{
            let result = try divideNumbers(numerator: number1,
                                         denominator: number2)
            print("\(number1) divided by \(number2) equals \(result)")
        }
        catch
        {
            print ("number2 is zero!")
        }
    }
  • Call the performDivision function.
    1. Type the following line after the end of the performDivision function definition:
      performDivision(10, 2)
    2. Observe the results of this program in the console. You should see the following line in the console:
      "10.0 divided by 2.0 equals 5.0\n"