What’s Right with Errors

The not-so-flattering state of Error Handling and Presentation, according to Google Images

You’re enjoying your app when suddenly an error message pops up, reading “Error: Something went wrong
Or you’re developing a new feature and attempting to interact with this outdated API that just returns NO when you call [Obscure.sharedManager doYourMagic]

And you’re frustrated!

Thanks for the intel…

We tend to focus on producing what’s supposed to happen in our product, rather than thinking of what could go wrong. It should just work!

But sometimes things don’t go to plan. It might be that the password we typed in doesn’t meet the security standards or a signup form that doesn’t tell us what we did wrong. It’s like a job we applied for and got rejected from without getting any feedback — it’s hard to know what to improve.

Which is why we should give our client (end-user or fellow developer) the opportunity to learn from their errors by giving them feedback.

Over the last few months at Deezer we have worked on refactoring components of our iOS app, including In-App Purchase and Authentication. The latter is one of the first interaction users have with our app, the former involves their money. So in both cases it’s very important to handle errors nicely, either internally or by clearly communicating them to the users.

Good news is, as we found out, there are plenty of tools available such as language features in Swift and modelling in Foundation, which are becoming increasingly compatible. There is even a clever trick for error presentation that our team ported to iOS from their Mac OS days.

Now, let’s make Errors 🎉

Swift Error

What is a Swift Error?

In Swift, errors are represented by values of types that conform to the Error protocol. This empty protocol indicates that a type can be used for error handling.

This means we’re free to use whichever implementation we want to modelize an error, be it an enum, a struct or an object.

Basic Syntax

Contrary to Objective-C where error handling was opt-in (we chose whether or not we want an API to notify us of an error), this is mandatory in Swift.

It is based on a throwing mechanism, similar to what’s found in other languages such as Java. A function that throws errors it generates or encounters is called in a do-try-catch statement.

Reacting to errors

Swift has pattern matching, allowing for some fancy code like this:

Beyond the fanciness, it allows to take precise action on specific errors.

?!

Considering we are talking about Swift, why haven’t we mentioned using a question mark or an exclamation point? Are Errors some second-class Swift citizens?

Well not at all, both our favourite punctuation signs are here: try? for getting optionals out of failable functions, and try! for disabling error propagation.

Some real-world examples:

try?:

Trying to parse a server response without knowning the format for sure, Codable-style

try!:

Things will go smoothly, you’ve included that json stub in your test resources

Cleaning up behind you

Swift provides a way to specify cleanup actions using the defer keyword.
For example, here is my routine preparing a talk:

I know that, should some kind of distraction — a feature to ship with my team or a concert to attend to — be stoping my regular progression, I’ll always will end up filling up my slides ✌️

You could think of defer as some Swift conterpart to Java’s finally; but you don’t have to use it in an error-handling / do-try-catch context. It’s just a block of code that gets executed at the end of the scope it is defined in.

Back to NSError

Now that we’ve seen what’s new and shiny in Swift, let’s dust off those good old NSErrors. You’ve probably encountered some of them and passed NULL more than [NSJSONSerialization JSONObjectWithData: options: error:] deserved 🙈
But do you really know NSError ?

An NSError object encapsulates information specific to an error, including the domain (subsystem) originating the error and the localized strings to present in an error alert.

Identification and Information

NSError is the model of an error in all the Foundation world, providing identification (domain and code) and informations (localized infos and context).

The domain is the general identifier: it is a unique string, usually defined in a reverse-DNS fashion: @"com.deezer.Deezer.subdomain".
The code is more precise about the error and there are several ones per domain, thus traditionally leading to an enum declaration:
typedef NS_ENUM(NSInteger, subdomainErrorCode) {subdomainErrorCode1,…n}.

Then comes the localized informations, which is directly-displayable, user friendly strings.

Recommended presentation of an NSError in UIAlertController

Two are for explaining the error (description, failureReason) and two are for recovering from it (recoverySuggestion, recoveryOptions).

localizedDescription: the main message to the client. It’s a complete sentence that should be sufficient to understand what went wrong.
localizedFailureReason: the cause detailed in the localized description.

localizedRecoverySuggestion: choices available for recovering from that error.
localizedRecoveryOptions: titles of actions (aforementioned choices) the client can take.

Although the last three can be nil, localizedDescription never is. It’s also important to point-out that NSError does its best to provide you with something to display.

Finally, some context can be available in the userInfo[NSUnderlyingErrorKey] (the NSError from an underlying layer, that has been wrapped by the layer you are calling). And also the custom domain data the error creator chose to add.

Properties, UserInfo dictionaries, DomainProvider

When you open up NSError.h you face lots UserInfo keys and similarly named properties.

such localized; much key; wow!

But which one should we use?

For setting values, you can either use the UserInfo dictionary when calling init(domain: code: userInfo:) or by calling NSError.setUserInforValueProvider(forDomain: provider:) (available(iOS 9.0, *)). Both mechanism rely on the various UserInfoKeys constants.

For accessing information, the preferred way is via the properties. This is because NSError tries really hard to give you something to work with. For example if no value is provided for the description, it combines the failure (@available(iOS 11.0, *)) and failureReason strings, if provided, and fallbacks at the very minimum to a combination of the domain and code.

All values are first looked up in the userInfo dictionary, fallback to the domain provider before eventually returning nil.

For custom information you could provide your property doing a job similar to NSError localized properties. But let’s KISS and just access the dictionary!

Syntactic Sugar

You’ve got it, NSError is all about giving information. So it’s best to be clear when giving it. Objective-C has a few macros to available to make your own NSError+YourDomain.[hm] self-documentingly crystal-clear. 👌

I give you NSErrorDomain, NS_ERROR_ENUM, NSErrorUserInfoKey.

Before…

A lot of NSString…

After!

All proper types

Note that the macro associates the codes to the domain, hence the shortening of the name.

NSErrors in Swift 4

But that’s not all about NS_ERROR_ENUM! Because it is imported in Swift as a specific type, properly defined NSError domains and code enums let the client benefit from all the completeness of NSError while writing first-class swift code.

While NSError initing remains very similar, their handling gets shortened and clarified. In the following example inspired by the revamp of our Authentication engine, we try to register a Deezer user by using Facebook authentication. This can fail if we can’t get enough information from Facebook to meet our needs. In this case our Facebook Authentication service can generate an error stating this problem and providing the obtained informations. This in turn can be used to pre-fill our registration form.

Here it is with just NS_ENUM

Lots of casting and checking

And now with NS_ERROR_ENUM!

Clear and to the point

Don’t try to fool us, authentication occurs asynchronously! Will this shiny syntax work in the real world?

Actually yes: because pattern-matching in Swift goes beyond error-handling, you can handle async errors just as easily:

Pattern-matching everywhere! 💁🏻‍♂️👨🏻‍🚀

Error Presentation

That’s about all we need to know about (NS)Error creation and handling. Now let see what 2 of our iOS Leads, Ashley and Guillaume, imagined for error presentation.

An Error Responder Chain on iOS

But first some macOS background: in Cocoa, the responder is based is the NSResponder class and handles not only the user interactions but also the presentation of errors. Each responder of the chain can presentError: itself and by default only forwards to the nextResponder. All are given the opportunity to customize an error when they’re informed they willPresentError:.

There is a quite simple way to mimic this behaviour and get an Error Responder Chain out of iOS’ UIResponder: Categories.

Here is all the code:

What’s a Responder Chain anyway?

On iOS the UIResponders are all the views, viewControllers, application, and applicationDelegate that took part in presenting something that the user interacts with. They, in reverse, are given a chance to respond to this interaction.

For example let’s imagine

  • an app,
  • with a main viewController,
  • that has an embedded viewController,
  • with a button targetting its printResponderChain() action.

The outptut is something like this:

<UIButton> ->: <UIView> ->: <PrintingViewController>
->: <UIView> ->: <UIView> ->: <MainViewController>
->: <UIWindow> ->: <UIApplication> ->: <AppDelegate>

Use the chain!

The most straightforward way to take profit of the chain is to use it as a centralized way to inform the user about an error. No matter which viewController received an error from the service it represents, you can always pop an AlertController to the user. Nice and Easy.

Building on this, you can take some partial / particular action at one point of the chain, and let some other link take care of the full presentation.

In this example, we both shake the button, and display a banner.

The architecture of the screen an AuthenticationViewController diplaying the various authentication methods, with a few children, including a LoginViewController managind the login and password fields and the “Log in” button.

To properly give feedback to the user, the LoginViewController first shakes its button, then passes the error to the chain, and the AuthenticationViewController which displays the banner.

Other children can focus on their part of the UI and take advantage of the banner error presentation, without implementing some other delegation.

Dismiss those Errors

We’re reaching the end of this article and might have encountered the word “error” more than in our own codebases: it’s probably time to dismiss those errors!

To do so, we can implement dismissError(_ error:) and remove any UI we displayed in presentError(_ error:). If you associate specific (ranges of) error codes to specific UI element, it’s easy to track where to present and dismiss any errors.

I decided to make this article to share what I discovered exploring the wonderland of error handling in iOS development. Like most of us out there, our little iOS Deezer app has evolved to a full-grown application that requires a whole team of devs to collaborate on. In order to carry on shipping features in a reliable product, we are continuously refactoring our code, defining a proper architecture out of the (controlled) chaos, exposing what can be done by a module and what can legitimately go wrong.

We have all we need to do so:

  • The overlooked NSError, here from the beginning, has plenty to offer. And I believe it is here to stay since Apple continues to invest in it (new APIs in the recent major iOS versions).
  • Swift has business errors baked in at the language level and every version comes with improved syntax and compatibility with NSError.

In a mixed Swift/Objective-C environment, I definitely recommend using NSError to convey data: it’s really expressive and works well with UIKit.
If you have ditched (or never coded) with Objective-C, there’s an extra step to model the errors with your own implementation of the Error protocol. Maybe we’ll code long enough to see a full-swift Error object!
I encourage you to embrace coding with Errors as Swift makes it really easy to write some expressive code with that paradigm.

A good place to start? Search for a guard statement in your codebase where you just return: chances are you just threw an Error under the carpet.
Go! Refactor it! You won’t even have to mess too much with the caller if you treat the error as an optional. So, why not give it a try?