Ghostboard pixel

Error Handling in Swift 2.0

Error Handling in Swift 2.0

Swift 2.0 has a new way of error handling. It uses a do-try-catch  syntax,  which is the replacement for NSError .  In this post we will discuss how to use this new syntax.

Defining an error

Before  an error can be thrown or catched, it must be defined. You can define an error in Swift by an enum that implements the new ErrorType protocol:

enum MyErrorType : ErrorType {
      case SomeErrorCause
      case AnotherErrorCause
}

Throwing an error

For a function to be able to throw an error, it must announce this in the function head  by the keyword throws :

func testFunction() throws {
        
}

Now this function can throw an error by using the keyword throw  and the mention of the concrete error type:

func testFunction() throws {
        
      throw MyErrorType.SomeErrorCause
        
}

Catching an error

If you just try to call this function, there will be a compiler error. Because the function announces that it is capable to throw an error,  you will need to catch the potential error:

do {
      try testFunction()
} catch {
      print("there was an error...")
}

By the way: Because the  do-try-catch syntax is similar to the do-while syntax, the latter is renamed to repeat-until.

Furthermore, in a do-try-catch  block you have the possibility to catch several errors:

do {
      try testFunction()
} catch MyErrorType.SomeErrorCause {
      print("there was an MyErrorType.SomeErrorCause-error...")
} catch MyErrorType.AnotherErrorCause {
      print("there was an MyErrorType.AnotherErrorCause-error...")
} catch {
      print("there was another error...")
}

However, similar to switch, a do-try-catch  block must be exhaustive. That means that all possible errors must be catched. Since the error throwing function does not announce what kind of errors it can throw, this means that all known errors must be catched. In practice this means that there need to be always a pure catch  case.

Cleaning Up

If a function throws an error, then it will return immediately. But because sometimes things needs to be done before a functions returns, there is the new keyword defer. For example, this clean-up could  be closing a file. With defer you can define a block of code that is always executed when a functions returns, no matter whether the function returns normally or due to an error.

You can define your defer  block anywhere in your function. Furthermore, it is also possible to define more than one defer  block. If you do so, all of these blocks will be executed in reverse order.

Take a look at an example:

func testFunction() throws {
      defer {
          print("clean-up")
      }  

      throw MyErrorType.SomeErrorCause
        
}

For more details, take a look at another blog post about this topic.

Error handling interoperability between Objective-C and Swift

Objectice-C is now 100% compatible to Swift’s new error handling. If you use NSError  in the right way, the functions’s head is automatically translated to the correct Swift syntax. Let’s look at an example:

#import "ObjectiveCTestClass.h"

@implementation ObjectiveCTestClass

- (BOOL) doSomethingRiskyWithAString:(NSString*) aString error:(NSError**)error {
    
    if (aString == nil) {
        NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
        [errorDetail setValue:@"Failed" forKey:NSLocalizedDescriptionKey];
        *error = [NSError errorWithDomain:@"aDomain" code:100 userInfo:errorDetail];
        return NO;
    }
    
    return YES;
}

@end

We have a test class with a function that satisfies two requirements:

  1. The function has as last parameter a pointer of type  NSError** .
  2. The function returns something – that means the return type is not nil .

Now if you declare the ObjectiveCTestClass.h in the BridgingHeader file, the function head is translated to

doSomethinhRiskyWithAString(aString: String!) throws -> Bool

That means, you can call the Objective-C function from Swift in the following way:

let objectiveCTestObject = ObjectiveCTestClass()
do {
      try objectiveCTestObject.doSomethingRiskyWithAString(nil)
} catch let error as NSError {
      print("Error: (error.domain)")
}

Note how you can get access to the NSError  pointer in the catch block.

The other way round, if you call a Swift function that throws an error from Objective-C, the function is translated to the NSError  syntax.

[thrive_text_block color=”blue” headline=”Conclusion”]

Swift’s new error handling model is a very important new feature. It is more intuitiv than the old NSError  way but also very powerful. Like always, the interoperability between Swift and Objective-C is very good. You should be able to adapt very quickly.

[/thrive_text_block]

References