read

ErrorType is a big thing is Swift, changing the way how errors are handled.

NSError is the Past

NSError was the way how errors are encapsulated, and passed along in methods and blocks/closures.

But it was not a good design.

It differentiates the errors using code (Int), and also domain (String).

And it passes more information in userInfo as a dictionary.

It works, but isn’t cool.

A bit of History: In the very beginning (in MacOS, way before any iOS), there is NSException and try-catch in Objective-C, but eventually NSError took over.

ErrorType

Then comes ErrorType in Swift.

It is merely a protocol.

And it does NOT have any method. It is an EMPTY PROTOCOL.

That means you can make any of your type a ErrorType, and have it being throw around.

Note: ErrorType can always be casted to NSError, magically. You can cast to NSError without any problem, but it don’t make much sense because you lose almost all information on the error.

If you choose to use ErrorType, forget about NSError. Unless the NSError comes from other frameworks/libraries, then you might want to handle and map to one of your custom ErrorType case. Or you can wrap the NSError in your custom ErrorType (see .OtherNSError below).

Custom ErrorType

Every app should have one (or more) custom AppError that subclass ErrorType.

It will usually be an enum (but you can use class and struct too, if you want), with different cases of errors, and init with different parameters (thanks to enum)!

enum AppError: ErrorType {
    case DivisionError
    case NetworkError(code: Int)
    case UnexpectedError(message: String)
    case OtherNSError(nsError: NSError)
}

It will be good to implement CustomStringConvertible, and return description accordingly for each error case.

try-catch-throw

I will not discuss more on how try-catch-throw works, because there are many, and I love this version:

Let it throw, let it throw~

An Example

But I will provide an example using Unbox library (a good JSON decoder) of how to catch it’s UnboxError (their custom ErrorType):

do {
    ...
} catch UnboxError.MissingKey(let key) {
    // Specific error case of a MissingKey
    print("Missing Key", key)
} catch let error as UnboxError {
    // Catch all other UnboxError
    // Because UnboxError conforms to CustomStringConvertible, we can print `description`
    print(error.description)
} catch let error as NSError {
    // Warning: Cast to NSError (details will be lost). Not recommended.
    print(error.localizedDescription)
}

Description of the Error

Given an error, we usually display error.localizedDescription to the user.

There are default implementations, but you will want to implement your own when you have meaningful messages that can be displayed. To do so, extend LocalizedError protocol.

extension AppError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .unsupportedCountry:
            return "This is what went wrong."
        default:
            return self.localizedDescription
        }
    }
}

There are other properties you may also extend to provide reasons and recovery.

Handling throw in closures

There are 2 ways as discussed in appventure.me:

  1. Using Result
  2. Using inner closure that throws

Using Result is simpler, and easier to read. It became popular as more developers use the Result, even though it is NOT in Swift standard library. It is a simple concept, and you can find a couple of different Result.swift in Github.

Result Type

Result is an enumeration that’s either a .Success(value) or a .Failure(error), and is a common pattern among Swift programmers.

Before Swift 2, it is a common way to deal with a result - either success or with error.

In Swift 2, try-catch is Apple’s endorsed way.

But Result still is good and more composable eg. you can pass Result in a closure

The concept of Result is very simple, and the shortest implementation is:

public enum Result<T, Error: ErrorType> {
    case Success(T)
    case Failure(Error)
}

But you may want to refer to a more complete implementation, which has more feature such as dematerialize to throw an error if it is a failure.

Many frameworks, such as Alamofire, will also write their own Result type. You cam probably write your own too.


Image

@samwize

¯\_(ツ)_/¯

Back to Home