Throwing and catching errors in Swift

In Swift, an error must be an object of a type that adopts the Error protocol, which has just two requirements: a String _domain property and an Int _codeproperty. The purpose of those properties is to help errors cross the bridge between Swift and Objective-C; in real life, you will be unaware of them (and in fact you won’t even see them listed in the Swift header). The object will be one of the following:

  • A Swift type that adopts Error. As soon as a Swift type formally declares adoption of the Error protocol, it is ready to be used as an error object; the protocol requirements are magically fulfilled for you, behind the scenes. Typically, this type will be an enum, which will communicate its message by means of its cases: different cases will distinguish different kinds of possible failure, perhaps with raw values or associated types to carry further information.
  • NSError. NSError is Cocoa’s class for communicating the nature of a problem; Swift extends NSError to adopt Error and bridges them to one another. If your call to a Cocoa method generates a failure, Cocoa will send you an NSError instance typed as an Error.

There are two stages of the error mechanism to consider - throwing an error, and catching an error:

  • Throwing an error aborts the current path of execution and hands an error object to the error mechanism.
  • Catching an error receives that error object from the error mechanism and responds in good order, with the path of execution resuming after the point of catching. In effect, we have jumped from the throwing point to the catching point.

To throw an error, use the keyword throw followed by an error object. That’s all it takes! The current block of code is immediately aborted, and the error mechanism takes over. However, to ensure that the throw command is used coherently, Swift imposes a rule that you can say throw only in a context where the error will be caught.

The primary context for throwing and catching an error is the do...catch construct. This consists of a do block and one or more catch blocks. It is legal to throw in the do block; an accompanying catch block can then be fed any errors thrown from within the do block. The do...catch construct’s schema looks like

do {
    statements // a throw can happen here
} catch errortype {
    statements
} catch {
    statements
}

A single do block can be accompanied by multiple catch blocks. Catch blocks are like the cases of a switch statement, and will usually have the same logic: first, you might have specialized catch blocks, each of which is designed to handle some limited set of possible errors; finally, you might have a general catch block that acts as the default, mopping up any errors that were not caught by any of the specialized catch blocks.