Understanding Error Handling in Swift 3

From Techotopia
Revision as of 04:21, 10 November 2016 by Neil (Talk | contribs) (Understanding Error Handling)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

PreviousTable of ContentsNext
Working with Array and Dictionary Collections in SwiftCreating an Interactive iOS 10 App


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


In a perfect world, a running iOS app would never encounter an error. The reality, however, is that it is impossible to guarantee that an error of some form or another will not occur at some point during the execution of the app. It is essential, therefore, to ensure that the code of an app is implemented such that it gracefully handles any errors that may occur. Since the introduction of Swift 2, the task of handling errors has become much easier for the iOS app developer.

This chapter will cover the handling of errors using Swift 3 and introduce topics such as error types, throwing methods and functions, the guard and defer statements and do-catch statements.


Contents


Understanding Error Handling

No matter how carefully Swift code is designed and implemented, there will invariably be situations that are beyond the control of the app. An app that relies on an active internet connection cannot, for example, control the loss of signal on an iPhone device, or prevent the user from enabling “airplane mode”. What the app can do, however, is to implement robust handling of the error (for example displaying a message indicating to the user that the app requires an active internet connection to proceed).

There are two sides to handling errors within Swift. The first involves triggering (or throwing) an error when the desired results are not achieved within the method of an iOS app. The second involves catching and handling the error after it is thrown by a method.

When an error is thrown, the error will be of a particular error type which can be used to identify the specific nature of the error and used to decide on the most appropriate course of action to be taken. The error type value can be any value that conforms to the ErrorType protocol.

In addition to implementing methods in an app to throw errors when necessary, it is important to be aware that a number of API methods in the iOS SDK (particularly those relating to file handling) will throw errors which will need to be handled within the code of the app.

Declaring Error Types

For the sake of an example, consider a method that is required to transfer a file to a remote server. Such a method might fail to transfer the file for a variety of reasons such as there being no network connection, the connection being too slow or the failure to find the file to be transferred. All of these possible errors could be represented within an enumeration that conforms to the Error protocol as follows:

enum FileTransferError: Error {
    case noConnection
    case lowBandwidth
    case fileNotFound
}

Once an error type has been declared, it can be used within a method when throwing errors.


Throwing an Error

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

A method or function declares that it can throw an error using the throws keyword. For example:

func transferFile() throws {
}

In the event that the function or method returns a result, the throws keyword is placed before the return type as follows:

func transferFile() throws -> Bool {
}

Once a method has been declared as being able to throw errors, code can then be added to throw the errors when they are encountered. This is achieved using the throw statement in conjunction with the guard statement. The following code declares some constants to serve as status values and then implements the guard and throw behavior for the method:

let connectionOK = true
let connectionSpeed = 30.00
let fileFound = false

enum FileTransferError: Error {
    case noConnection
    case lowBandwidth
    case fileNotFound
}

func fileTransfer() throws {

    guard connectionOK else {
        throw FileTransferError.noConnection
    }

    guard connectionSpeed > 30 else {
        throw FileTransferError.lowBandwidth
    }

    guard fileFound else {
        throw FileTransferError.fileNotFound
    }
}

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Within the body of the method, each guard statement checks a condition for a true or false result. In the event of a false result, the code contained within the else body is executed. In the case of a false result, the throw statement is used to throw one of the error values contained in the FileTransferError enumeration.

Calling Throwing Methods and Functions

Once a method or function is declared as throwing errors, it can no longer be called in the usual manner. Calls to such methods must now be prefixed by the try statement as follows:

try fileTransfer()

In addition to using the try statement, the call must also be made from within a do-catch statement to catch and handle any errors that may be thrown. Consider, for example, that the fileTransfer method needs to be called from within a method named sendFile. The code within this method might be implemented as follows:

func sendFile() -> String {

    do {
        try fileTransfer()
    } catch FileTransferError.noConnection {
        return("No Network Connection")
    } catch FileTransferError.lowBandwidth {
        return("File Transfer Speed too Low")
    } catch FileTransferError.fileNotFound {
        return("File not Found")
    } catch {
        return("Unknown error")
    }

    return("Successful transfer")
}

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

The method calls the fileTransfer method from within a do-catch statement which, in turn, includes catch conditions for each of the three possible error conditions. In each case, the method simply returns a string value containing a description of the error. In the event that no error was thrown, a string value is returned indicating a successful file transfer. Note that a fourth catch condition is included with no pattern matching. This is a “catch all” statement that ensures that any errors not matched by the preceding catch statements are also handled. This is required because do-catch statements must be exhaustive (in other words constructed so as to catch all possible error conditions).

Accessing the Error Object

When a method call fails, it will invariably return an Error object identifying the nature of the failure. A common requirement within the catch statement is to gain access to this object so that appropriate corrective action can be taken within the app code. The following code demonstrates how such an error object is accessed from within a catch statement when attempting to create a new file system directory:

do {
    try filemgr.createDirectory(atPath: newDir,
                        withIntermediateDirectories: true,
                        attributes: nil)
    } catch let error {
            print("Error: \(error.localizedDescription)")
}

Disabling Error Catching

A throwing method may be forced to run without the need to enclose the call within a do-catch statement by using the try! statement as follows:

try! fileTransfer

In using this approach we are informing the compiler that we know with absolute certainty that the method call will not result in an error being thrown. In the event that an error is thrown when using this technique, the code will fail with a runtime error. As such, this approach should be used sparingly.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Using the defer Statement

The previously implemented sendFile method demonstrated a common scenario when handling errors. Each of the catch clauses in the do-catch statement contained a return statement that returned control to the calling method. In such a situation, however, it might be useful to be able to perform some other task before control is returned and regardless of the type of error that was encountered. The sendFile method might, for example, need to remove temporary files before returning. This behavior can be achieved using the defer statement.

The defer statement allows a sequence of code statements to be declared as needing to be run as soon as the method returns. In the following code, the sendFile method has been modified to include a defer statement:

func sendFile() -> String {

    defer {
        removeTmpFiles()
        closeConnection()
    }

    do {
        try fileTransfer()
    } catch FileTransferError.NoConnection {
        return("No Network Connection")
    } catch FileTransferError.LowBandwidth {
        return("File Tranfer Speed too Low")
    } catch FileTransferError.FileNotFound {
        return("File not Found")
    } catch {
        return("Unknown error")
    }

    return("Successful transfer")
}

With the defer statement now added, the calls to the removeTmpFiles and closeConnection methods will always be made before the method returns, regardless of which return call gets triggered.

Summary

Error handling is an essential part of creating robust and reliable iOS apps. Since the introduction of Swift 2 it is now much easier to both trigger and handle errors. Error types are created using values that conform to the ErrorType protocol and are most commonly implemented as enumerations. Methods and functions that throw errors are declared as such using the throw keyword. The guard and throw statements are used within the body of these methods or functions to throw errors based on the error type.

A throwable method or function is called using the try statement which must be encapsulated within a do-catch statement. A do-catch statement consists of an exhaustive list of catch pattern constructs, each of which contains the code to be executed in the event of a particular error being thrown. Cleanup tasks can be defined to be executed when a method returns through the use of the defer statement.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
Working with Array and Dictionary Collections in Swift 2Creating an Interactive iOS 10 App