Exceptions and Error Handling

RemObjects Swift combines the error handling syntax, available since Swift 2.0, with the handling of real platform Exceptions.

Both error and exception handling is done with the do/catch keyword combination, and cleanup code (the traditional finally block of exception handling) are performed with an independent defer statement.

  • do/catch work pretty much as exception handling works in the other languages – the code inside the do scope is protected against failures, and one or more catch clauses can be provided where execution will continue if an exception or error occurs. Separate catch clauses can be defined to catch different kinds of errors, by class, or by more complex Pattern.

  • defer blocks can be used to specify code that will run at the end of the current scope, regardless of whether an error or exception occurred, or whether the method exited prematurely via a simple return. This makes defer statements incredibly useful even when error handling is not involved.

Exceptions vs. Errors

The do/catch syntax combines handling of both errors (a concept which does not translate to the other platforms/languages) and exceptions in the following ways.

Exceptions

RemObjects Swift uses the regular do/catch syntax to protect code against exceptions, and executes the closest matching catch block when an exception occurs. Keywords aside, this is no different than regular try/catch or try/except blocks in C# or Oxygene.

If no variable is provided for the catch block, a default error variable will be in scope, containing the current exception. It can be re-thrown with the throw keyword.

Note that the try (or try!) keyword is not used or necessary for exception handling. It can be specified and will be ignored. This is because any statement can, potentially, throw an exception, and making every statement require a try would be cluttery.

do {
    let x = Int32.Parse("Five");
} catch {
    println("could not parse string, failed with \(error)")
}

Errors

In addition, the try or try! keywords can be used inside the do scope to call methods that follow Cocoa's and Swift's pattern of returning an Error or NSError value, explicitly or by being declared with throws in Swift.

When called with the try keyword, these methods drop the last Error parameter, and their result is converted to be non-nullable (for functions that return a nullable value) or to be void (for functions that return a Boolean). When the function call returns nil or false, the remainder of the do scope will be skipped, and execution will continue with the closest matching catch clause for the received NSError. No actual platform exception will be raised.

These methods can also be called the "old fashioned" way, without the try keyword, and handling the return value and any returned error value will be up to the calling code (just as it would be from other languages). The calls will then not participate in any error handling logic provided by a potential do/catch.

do {
    try NSFileManager.defaultManager.contentsOfDirectoryAtPath(path)
} catch {
    println("could not read directory, failed with \(error)")
}

vs.

var error: NSError?
if !NSFileManager.defaultManager.contentsOfDirectoryAtPath(path, error: &error) {
    println("could not read directory, failed with \(error)")
}

The NSError Pattern

Exceptions can happen on all platforms (including Cocoa). Errors are limited to three specific scenarios:

  • Methods that return a nullable result value and a nullable __out NSError value as last parameter, on Cocoa.
  • Methods that return a Bool result value and a nullable __out NSError value as last parameter, on Cocoa.
  • Methods declared in Swift, using the throw keyword.

try?

The try? syntax is also fully supported, for both exceptions and errors, and will convert any exception or error into a nil value:

let x = try? Int32.Parse("Five");
//x will be nil

Converting Errors to Exceptions

RemObjects Swift also allows you to use the try or try! keywords to make calls to methods that follow the NSError pattern, without a do/catch clause. If the method containing the try! statement itself follows the NSError pattern, any error received will be passed on to the caller of the current method. If the method does not follow the pattern, any error will be wrapped in a platform exception and thrown up the call stack.

Once again, try? will catch errors and convert them into a nil result.

func countFilesInDirectory() -> Int throws {
    let files = try! NSFileManager.defaultManager.contentsOfDirectoryAtPath(path)
    return files.count
}

Throwing Errors and Exceptions

The throw keyword can be used to throw an Exception (all methods) or an Error (inside methods that follow the NSError Pattern).

Legacy Exception Handling Language Extension

Note that the temporary Exception Handling Language Extension we provided for Swift 1.0, using __try, __finally and __catch, has been deprecated as of Elements version 8.2, and will be removed in a future update.

See Also