The difference between iOS Exceptions and Errors

| 7 min. (1320 words)

As developers working with any language, it’s important to know what can go wrong in your code. This is a beginner’s overview of exceptions and errors in iOS – 2 different constructs that you’ll want to distinguish between.

Exceptions

In short, exceptions cause applications to crash if left unhandled. They generally occur when trying to perform an operation on an object incorrectly, such as using an out-of-bounds index to access an array item, or passing nil to a method that doesn’t accept it. In other words, they are caused by developer mistakes.

Unhandled exceptions in your published apps must be avoided at all costs. Your customers will not be pleased if your app crashes, and your business can suffer from it. If exceptions are so detrimental, then why do they exist? They are warnings to developers that a serious coding issue has occurred and needs to be fixed. They are expected to occur during development of you application, and provide information that will help you solve the issue before shipping your app.

Objective-C provides a single class to hold exception information called NSException. This class contains 3 key points of information:

  • name identifies the type of exception that has occurred. As mentioned above, there is only one exception class, rather than different classes to represent different problems – so the name property is used as the high level categorization. Some common exception names you may come across include NSInvalidArgumentException and NSGenericException. The main predefined cocoa exception names are defined in NSException.h.
  • reason is a short explanation of why the exception has been thrown. For example “+[Class Selector] unrecognized selector sent to class 0x10866fb88”.
  • userInfo is an NSDictionary of additional information that can help to debug the problem. I haven’t seen any use of this from exceptions thrown by the cocoa framework, it seems to be more used by developers throwing their own exceptions to provide values of properties and variables and so on.

Throwing and catching exceptions

You shouldn’t ever need to throw exceptions in you application, however they can be useful if you’re writing a library – be it for your app or for other developers. If someone calls a method in your library with invalid arguments, throwing an exception is a way of telling them they’re doing it wrong. In case you ever need to do this, exceptions can be created with the NSException class factory method shown below which takes the 3 key bits of information to help identify and debug the problem. The exception can then be thrown using the @throw directive:

NSException *e = [NSException exceptionWithName:@"ParseException" reason:@"The given document is missing the elements section." userInfo:nil];
@throw e;

Wherever possible, you should check or/and constrain arguments before calling methods to avoid them throwing exceptions. If in doubt, or if not possible, you can use @try @catch blocks to handle exceptions however appropriate.

@try {
  // Some potentially cataclysmic operation
}
@catch(NSException e) {
  if ([[e name] isEqualToString:@"ParseException"]) {
    // handle this specific exception
  }
  else {
    @throw;
  }
}
@finally {
  // clean up memory
}

In the @catch block above you can see a use for the NSException name property. By checking the name of the exception, you can handle different types of exceptions that may occur from the same operation in different ways. Using a @throw; directive inside a @catch block will cause the exception to be rethrown where it can be handled further up the calling chain. The @finally block at the end will run its code regardless of if an exception occurred. This is useful for cleaning up memory or anything else that you need to ensure is performed before exiting the method.

Exception reporting with Raygun

So I’ve been going on about making sure you avoid or handle exceptions before releasing your application, but is your published app really bug free? If you have a very complex code base or/and you use third party libraries that throw exceptions you’re unaware of, then probably not. Once your application is out there being used by your customers all around the world, it would be great to know whether or not there are still exceptions are crashing your application!

Awareness of exceptions in your published apps can be achieved by integrating Raygun4iOS into your application. This listens for exception signals that crash the application, and then sends the information about the crash to the Raygun cloud service. There you can see how often each exception occurs, how many users are being affected by them and all the information you need including the stack trace to fix the bug for the next release. Raygun4iOS can be installed either with CocoaPods, or manually – instructions can be found here. A single line of code is then used to enable unhandled exception reporting for your whole app:

[Raygun sharedReporterWithApiKey:@"YOUR_API_KEY_HERE"];

If you want to know about an exception that you’ve handled in a @try @catch block, you can manually send the exception object with the Raygun reporter as follows:

[[Raygun sharedReporter] send:exception];

Errors

Errors are used in quite a different way than exceptions. They don’t get thrown, and they don’t cause the application to crash. Instead, they are created to hold information about a failure, and then bubbled up through calling methods where it may be ‘handled’ in some way such as displaying a message to the user. Failures that result in errors being created can be considered common or even expected issues. A lot of the built-in cocoa errors are related to file system issues – such as files not being found, or running out of memory while writing. errors in iOS are represented by the NSError class which provides these 3 properties:

  • domain is a high level grouping of errors.
  • code is used to distinguish different types of errors within a domain.
  • userInfo is an NSDictionary containing additional information about the error.

The documentation here has more information about these 3 properties, including constants that you’ll commonly see used.

Working with errors

You may come across some methods that have a direct (NSError *) or indirect (NSError **) reference to an NSError as the last argument. This is how errors get passed up though method chains. An important thing to note is that methods with an indirect reference are expected to return NO or nil to indicate whether or not the operation was successful. You should always check this first to determine if an error has occurred before using the NSError object. Here is a simple usage example of checking an error.

NSError *error;
BOOL success = [writer writeTo:filePath withData:data error:&error];

if (success == NO) {
  // display an alert to the user
}

With that in mind, make sure any of your methods that create errors can be used in the same way.

- (BOOL)writeTo:(NSString *)path withData:(NSString *)data error:(NSError **)error {
  *error = [NSError errorWithDomain:domain code:code userInfo:nil];
  return NO;
}

Also, make sure you either use existing domains and codes OR your own domain and codes – don’t mix and match an existing domain with your own code within the same NSError object.

Sending error information to Raygun

If you want to know about whether or not and how often certain errors are occurring in your published applications, you can do so by manually sending them with Raygun4iOS. Simply use the following code to send an NSError instance to Raygun. You can optionally send a list of string tags, or an NSDictionary of custom data to further help understand what’s happening within your app.

[[Raygun sharedReporter] sendError:error withTags:nil withUserCustomData:nil];

Remember

Exceptions are caused by programming faults, and must be fixed or handled. Errors are expected to happen from time to time and should be dealt with accordingly. Raygun can be used to get insight into your published applications, so that you can fix up any missed exceptions that crash your app.

There’s lots more to learn about iOS exceptions and errors in the iOS documentation.