Channels ▼
RSS

Design

Handling Errors in iOS and OS X with Cocoa


Whatever the cause, product errors make an application unpleasant to use if they are not handled properly. They can lead to a crash or freeze during normal use, and they can endanger user data. Users become frustrated and developers look incompetent. To guard against product errors, developers should make Cocoa projects error-tolerant. This means being able to encapsulate an error as it occurs, to dispatch it, and to respond to it properly. Cocoa provides us with the tools to accomplish this in the form of the NSError and NSResponder classes. In this article, I examine how to create an error object and how to dispatch it on the application's event loop. I will also explore how to respond to the error, as well as how to alter it, and finally, how to assign the right recovery routine to an error.

To follow, you'll need a basic understanding of Objective-C. All featured code is applicable to both OS X and iOS projects, unless stated otherwise.

The Error Object

The NSError class (Figure 1) serves as the basis for an error object. It lets the error be identified and categorized. It can carry a collection of localized strings needed to describe the error. It can even invoke a recovery routine for the given error.


Figure 1.

NSError offers two ways to create an error object. One way is with the factory method errorWithDomain:code:userInfo:. This method takes three input arguments: the error domain, its unique code ID, and a support dictionary. It can accept a nil in place of the support dictionary. It marks the error object for autorelease for easy disposal.

NSString *eTyp;
NSInteger *eID;
NSError *eErr;

eTyp = [NSString stringWithString:@"FooErrType"];
eID = 0xf00;
eErr = [NSError errorWithDomain:eTyp 
	code:eID userInfo:nil];

Another way to create the NSError object is with the instance method initWithDomain:code:userInfo:. This also takes the same three arguments as the factory method. It does not, however, mark the resulting error object for autorelease. It should be used together with an alloc message. The error object should be disposed of with a release message.

eErr = [[NSError alloc] initWithDomain:eTyp 
	code:eID userInfo:nil];
//...
[eErr release];

The NSError class uses three accessors to retrieve parts of the error data. The accessor code returns the code ID as an NSInteger. The accessor domain returns the error domain as an NSString. And the accessor userInfo returns the support dictionary as an NSDictionary object.

NSError also has accessors for the support dictionary. For instance, the accessor localizedDescription returns a concise description of the error. The accessor localizedFailureReason gives the cause of that error. And the accessor localizedRecoverySuggestion describes the recovery routine.

The accessor localizedRecoveryOption returns an NSArray object. This array object carries one or more text objects used for populating an alert dialog. The accessor recoveryAttempter returns a pointer to the ObjC object supplying the recovery routine.

Preparing the Error Object

Creating an NSError object is straightfoward, but how is the data be prepared for the error object? There are three principal steps to the process.

1. Pick the error domain.

An error domain describes the general locale of the error. There are four possible domains. Errors caused by low-level code, such as those from kernel routines, are from the Mach domain (NSMachErrorDomain). Those made by calls to a POSIX library are from the POSIX domain (NSPOSIXErrorDomain). Errors that came from the Carbon library have an OS status domain (NSOSStatusErrorDomain). And those that came from a Cocoa class have a Cocoa domain (NSCocoaErrorDomain). Each error domain gets its own constant NSString object. We can, of course, define our own domain when developing a custom ObjC framework.

2. Choose the appropriate error code.

This is usually a signed integer (NSInteger). Most error codes are declared by the same header files that can be accessed from any C routine. For instance, error codes for the Mach domain are in the file /usr/include/mach/kern_return.h. Those for the POSIX domain are in /usr/include/sys/errno.h. Carbon error codes are declared by the file MacErrors.h, which sits inside the framework bundle CarbonCore.framework. Similarly, Cocoa error codes are declared by their respective frameworks: Foundation, AppKit, Core Data, and so on.

3. Assemble the support dictionary.

The support dictionary provides the information needed to further describe the error. By default, it carries localized strings that can be displayed in an alert dialog or written to a log file. Supplying a support dictionary is optional.

Each dictionary entry is assigned a unique key. Cocoa itself declares five NSString constants to serve as keys for the default entries. The error description gets the constant NSLocalizedDescriptionKey, the failure reason NSLocalizedDescriptionKey. The array object gets NSLocalizedRecoveryOptionsErrorKey, the recovery description NSLocalizedRecoverySuggestionErrorKey. And the optional recovery object gets NSRecoveryAttempterErrorKey.
Listing One demonstrates how to create a basic error object. This sample snippet starts by importing the file Foundation.h. It creates an instance of the Cocoa class NSFileManager (line 9) and declares the path to the file "Foo.text" (lines 10-11). Then it sends a fileExistsAtPath: message to tMgr to check for the file (line 12). If the file exists, the snippet runs the desired file operation. If not, it creates an NSError object, sets the error code to 4 (missing file or NSFileNoSuchFileError), the error domain to NSCocoaErrorDomain (lines 19-21). Notice this error object gets a nil for its support dictionary.

Listing One

#import <Foundation/Foundation.h>
.
.
.
NSFileManager *tMgr;
NSString *tPth;
BOOL tChk;

tMgr = [NSFileManager defaultManager];
tPth = [NSString stringWithString:@"~/Documents/Foo.text"];
tPth = [tPth stringByExpandingTildeInPath];
tChk = [tMgr fileExistsAtPath:tPth];
if (tChk)
{
	// perform a file operation...
}
else
{
	NSError *eErr;
	eErr = [NSError errorWithDomain:NSCocoaErrorDomain 
	 	code:4 userInfo:nil];
	
 	// dispatch the error object...

Listing Two shows how to prepare the support dictionary. First, the snippet creates an instance of NSMutableDictionary (line 10). It prepares two constant NSString objects and stores them under their respective keys (lines 13-19). Then the snippet creates the NSError object, passing the support dictionary (eData) to the factory method (lines 21-22).

Listing Two

#import <Foundation/Foundation.h>
.
.
.
NSMutableDictionary *eData;
NSString *eDesc, *eFail;
NSError *eErr;

// create a dictionary object
eData = [NSMutableDictionary dictionaryWithCapacity:2];

// prepare the support dictionary
eDesc = [NSString stringWithString:@"Could not locate the file Foo.text."];
[eData setObject:eDesc 
	forKey:NSLocalizedDescriptionKey];

eFail = [NSString stringWithString:@"The file simply did not exists"];
[eData setObject:eFail 
	forKey:NSLocalizedFailureReasonErrorKey];

eErr = [NSError errorWithDomain:NSCocoaErrorDomain 
	code:4 userInfo:eData];
	
// dispatch the error object...

The NSError object is immutable once created. Its data cannot be changed, only copied into or replaced with a separate error object. Several Cocoa classes also return NSError objects as part of their method calls. Use those error objects instead of creating your own.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video