Upcoming and OnDemand Webinars View full list

Error Handling in Swift 2.0

Juan Pablo Claude

When Apple announced Swift 2.0 at this year’s WWDC, Swift’s main architect, Chris Lattner,
indicated that the 2.0 update to the language focused on three main areas: fundamentals, safety and beautiful code.
Out of the list of new features, improvements, polishes and beautifications, one that may impact your Swift 1.x
code the most is error handling.

That’s because you cannot opt out of it. You must embrace error handling if you want to write Swift 2.0 code, and it will change the way you interact with methods that use NSError in the Cocoa and Cocoa Touch frameworks.

A Bit of History: Humble Beginnings

As we all know, Swift was created as a modern replacement for Objective-C, the lingua franca for writing OS X and iOS applications. In its earliest releases, Objective-C did not have native exception handling. Exception handling was added later through the NSException class and the NS_DURING, NS_HANDLER and NS_ENDHANDLER macros. This scheme is now known as “classic exception handling,” and the macros are based on the setjmp() and longjmp() C functions.

Exception-catching constructs looked as shown below, where any exception thrown within the NS_DURING and NS_HANDLER macros would result in executing the code between the NS_HANDLER and NS_ENDHANDLER macros.

NS_DURING
    // Call a dangerous method or function that raises an exception:
    [obj someRiskyMethod];
NS_HANDLER
    NSLog(@"Oh no!");
    [anotherObj makeItRight];
NS_ENDHANDLER

A quick way to raise an exception is (and this is still available):

- (void)someRiskyMethod
{
    [NSException raise:@"Kablam"
                format:@"This method is not implemented yet. Do not call!"];
}

As you can imagine, this artisanal way of handling exceptions caused a lot of teasing for early Cocoa programmers.
However, those programmers kept their chins high, because they rarely used it. In both Cocoa and Cocoa Touch,
exceptions have been traditionally relegated to mark catastrophic, unrecoverable errors, such as programmer errors. A good
example is the -someRiskyMethod above, that raises an exception because the implementation is not ready. In the Cocoa
and Cocoa Touch frameworks, recoverable errors are handled with the NSError class discussed later.

Native Exception Handling

I guess the teasing arising from the classic exception handling in Objective-C got bothersome enough that Apple released
native exception handling with OS X 10.3, before any iOS version. This was done by essentially grafting C++ exceptions
onto Objective-C. Exception handling constructs now look something like this:

@try {
    [obj someRiskyMethod];
}
@catch (SomeClass *exception) {
    // Handle the error.
    // Can use the exception object to gather information.
}
@catch (SomeOtherClass *exception) {
    // ...
}
@catch (id allTheRest) {
    // ...
}
@finally {
    // Code that is executed whether an exception is thrown or not.
    // Use for cleanup.
}

Native exception handling gives you the opportunity to specify different @catch blocks for each exception type, and
a @finally block for code that needs to execute regardless of the outcome of the @try block.

Even though raising an NSException works as expected with native exception handling, the more explicit way to throw
an exception is with the @throw <expression>; statement. Normally you throw NSException instances, but any object may
be thrown.

NSError

Despite the many advantages of native versus classic exception handling in Objective-C, Cocoa and Cocoa Touch developers still rarely use exceptions, restricting them to unrecoverable programmer errors. Recoverable errors use the NSError class that predates exception handling. The NSError pattern was also inherited by Swift 1.x.

In Swift 1.x, Cocoa and Cocoa Touch methods and functions that may fail return either a boolean false or nil in place of an object to indicate failure. Additionally, an NSErrorPointer is taken as an argument to return specific information about the failure. A classic example:

// A local variable to store an error object if one comes back:
var error: NSError?
// success is a Bool:
let success = someString.writeToURL(someURL,
                                    atomically: true,
                                    encoding: NSUTF8StringEncoding,
                                    error: &error)
if !success {
    // Log information about the error:
    println("Error writing to URL: (error!)")
}

Programmer errors can be flagged with the Swift Standard Library function fatalError("Error message") to log an error message to the console and terminate execution unconditionally. Also available are the assert(), assertionFailure(), precondition() and preconditionFailure() functions.

When Swift was first released, some developers outside of Apple platforms readied the torches and pitchforks. They claimed Swift could not be a “real language” because it lacked exception handling. However, the Cocoa and Cocoa Touch communities stayed calm, as we knew that NSError and NSException were still there. Personally, I believe that Apple was still pondering the right way to implement error/exception handling. I also think that Apple deferred opening the Swift source until the issue was resolved (remember the pitchforks?). All this has been cleared up with the release of Swift 2.0.

Error Handling in Swift 2.0

In Swift 2.0, if you want to throw an error, the object thrown must conform to the ErrorType protocol. As you may have expected, NSError conforms to this protocol. Enumerations are used for classifying errors.

enum AwfulError: ErrorType {
    case Bad
    case Worse
    case Terrible
}

Then, a function or method is marked with the throws keyword if it may throw one or several errors:

func doDangerousStuff() throws -> SomeObject {
    // If something bad happens throw the error:
    throw AwfulError.Bad

    // If something worse happens, throw another error:
    throw AwfulError.Worse

    // If something terrible happens, you know what to do:
    throw AwfulError.Terrible

    // If you made it here, you can return:
    return SomeObject()
}

In order to catch errors, a new do-catch statement is available:

do {
    let theResult = try obj.doDangerousStuff()
}
catch AwfulError.Bad {
    // Deal with badness.
}
catch AwfulError.Worse {
    // Deal with worseness.
}
catch AwfulError.Terrible {
    // Deal with terribleness.
}
catch ErrorType {
    // Unexpected error!
}

The do-catch statement has some similarities with switch in the sense that the list of caught errors must be exhaustive and you can use patterns to capture the thrown error. Also notice the use of the keyword try. It is meant to explicitly label a throwing line of code, so that when you read the code you can immediately tell where the danger is.

A variant of the try keyword is try!. That keyword may be appropriate for those programmer errors again. If you mark a throwing call with try!, you are promising the compiler that that error will never happen and you do not need to catch it. If the statement does produce an error, the application will stop execution and you should start debugging.

let theResult = try! obj.doDangerousStuff()

Interacting with the Cocoa and Cocoa Touch Frameworks

The issue now is, how do you deal with grandpa’s NSError API in Swift 2.0? Apple has done a great job of unifying behavior in Swift 2.0, and they have prepared the way for future frameworks written in Swift.

Cocoa and Cocoa Touch methods and functions that could produce an NSError instance have their signature automatically converted to Swift’s new error handling.

For example, this NSString initializer has the following signature in Swift 1.x:

convenience init?(contentsOfFile path: String,
                  encoding enc: UInt,
                  error error: NSErrorPointer)

In Swift 2.0 the signature is converted to:

convenience init(contentsOfFile path: String,
                 encoding enc: UInt) throws

Notice that in Swift 2.0, the initializer is no longer marked as failable, it does not take an NSErrorPointer argument, and it is marked with throws to explicitly indicate potential failures. An example using this new signature:

do {
    let str = try NSString(contentsOfFile: "Foo.bar",
                           encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
    print(error.localizedDescription)
}

Notice how the error is caught and cast as an NSError instance, so that you can access information with its familiar API. As a matter of fact, any ErrorType can be converted to an NSError.

Finally, What about @finally?

Attentive readers may have noticed that Swift 2.0 introduced a new do-catch statement, not a do-catch-finally. How do you specify code that must be run regardless of errors? For that, you now have a defer statement that will delay execution of a block of code until the current scope is exited.

// Some scope:
{
    // Get some resource.

    defer {
        // Release resource.
    }

    // Do things with the resource.
    // Possibly return early if an error occurs.

} // Deferred code is executed at the end of the scope.

Swift 2.0 does a great job of coalescing the history of error handling in Cocoa and Cocoa Touch into a modern idiom that will feel familiar to many programmers. Unifying behavior leaves the Swift language and the frameworks it inherits in a good position to evolve.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project