oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Understanding Exceptions and Handlers in Cocoa

by Jose Cruz


Most, if not all, software products will have errors. These errors may come from design flaws in the product. They may be due to problems between the product and the underlying system. They may be caused by unexpected user actions. Whatever the origin, errors do not give a good user experience.

Users, however, do not only judge a software product by the errors it generates. They also judge the product on how it handles those errors. For instance, they see a product that crashes or freezes often during normal usage as poorly made. They may also have the same view of a product that displays frequent error messages. And they will dislike a product that loses some, if not all, of their data after an error.

On the other hand, these users may find a software product that still works even after an error as desirable. They may also prefer a product that exits gracefully after a fatal error. In order for software products to have such features, they will have to use an exception handling system.

This article focuses on how to use Cocoa to build an exception handling system. First, it shows the classes and keywords that make up that system. Next, it shows how to prepare and raise a Cocoa exception and then how to intercept and process it. Finally, it talks about the issue of uncaught exceptions and the concept of a default exception handler.

Exceptions in Cocoa

An exception handling system is a set of signals and code that handles an unexpected error. First, the affected code raises a signal known as an exception. The signal moves outwards until it reaches a code segment known as a handler. The handler tries to process the error or, failing that, sends it to the next available handler. But if the signal reaches the topmost handler, it becomes an uncaught exception. The topmost or default handler saves any user data and does any clean-up chores before it shuts down the product.

The Cocoa framework provides the means to build a robust exception system. Not only can that system detect and process errors, it can also provide information about those errors. The class that makes these possible is the NSException class.

The NSException Class

The NSException class is the main component of the Cocoa exception system. It directly derives from the NSObject class (Figure 1). It has three private properties: name and reason, which are both NSStrings; and userInfo, which is an NSDictionary.

The NSException class
Figure 1. The NSException class.

The name property is the unique ID of the exception. You can use a predefined name from the framework, or you can provide your own (See the Predefined Exceptions section later in this article). The reason property describes the cause of the exception. Finally, the userInfo property contains more information about the exception.

Preparing an NSException

To create an instance of NSException, send an exceptionWithName:reason:userInfo: message to the class. The following example creates an NSException named DemoException. It also sets the reason property to the NSString "A test exception". It then stores the instance into the variable anException. Notice that the userInfo property is set to a nil value.

NSException    *anException;
anException = [NSException exceptionWithName:@"DemoException" 
        reason:@"A test exception"

If you want NSException to have more data about the error, use the userInfo property to contain the data. Listing 1 shows one example of how this is done. First, the dictionaryWithObjectsAndKeys: message creates the NSDictionary instance aUserInfo. It initializes the instance with data from aLabel and anOutput. Next, the exceptionWithName:reason:userInfo: message creates an instance of NSException. It then sets the userInfo property to aUserInfo.

Listing 1. Setting the userInfo property
NSDictionary    *aUserInfo;
NSString    *aLabel;
NSNumber     *anOutput;
aLabel = [NSString stringWithString:@"The last output value:"];
anOutput = [NSNumber numberWithLong:987654321];
aUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
        aLabel, @"state",
        anOutput, @"output", nil];
anException = [NSException exceptionWithName:@"anException" 
            reason:@"A test exception with more data"

Suppose you already have an instance of NSException. To update the instance with new error data, use the initWithName:reason:userInfo: message. The following example shows the message changing the name and reason properties of the NSException instance, anException.

[anException initWithName:@"newError" 
            reason:@"This is a new error" 

The following are a few things for you to keep in mind when creating an instance of the NSException class.

  • Do not use a new or an alloc message to create an instance. The class will only return a nil in response to either message (see below). Messages sent to a nil will be ignored.
  • anException = [NSException new];
    // -- returns a nil
    anException = [NSException alloc];
    // -- also returns a nil
  • Do not use a release or an autorelease message to dispose an NSException. All instances of NSException are placed in the main autorelease pool. Manually disposing an instance will result in a SIGSEGV error.
  • Do not use a retain message to preserve an NSException. It will prevent the autorelease pool from disposing the instance. This will only result in a subtle memory leak.

Raising an NSException

To send an NSException on its way, pass a raise message to the instance (see below). The instance first locates the nearest exception handler. Next, it starts a longjump operation, which saves the current code state, and transfers control to the handler. The handler then tries to process the exception.

[anException raise];

If the instance fails to locate a handler, it goes straight to the default exception handler. The section When Handlers Fail covers the default handler in more detail.

You can also pass data to the NSException when you send it a raise message. The data is then stored into the reason property as a formatted NSString. There are two ways you can pass data when raising an NSException. One way is to use the raise:format:, message. The first argument of the message is the unique ID. The second argument is the C-style format string as an NSString. The third is a fixed list of data values. In short, this message works similarly to the printf() C-function.

The following example raises an NSException named "Invalid value". It sets the reason property of the raised instance to the phrase "The value is 3054". It also sets the userInfo property of that instance to nil.

NSString    *aFormat;
long        aValue;
aFormat = [NSString stringWithString:@"The value is %d"];
aValue = 3054;
[NSException raise:@"Invalid value" format:aFormat, aValue];

Another way is to use a raise:format:arguments: message. Again, the first argument of the message is the unique ID. The second argument is the C-style format string. The third is a va_list variable that refers to a variable list of arguments. This message works similarly to the vprintf() C-function.

For example, assume that the Cocoa class aClass has the method demoMethod, as shown in the following code. The input arguments of demoMethod start with the integer theCount and end with an ellipsis. This means the method has a variable list of input arguments. Since Objective-C reads the list from right to left, theCount is the last argument of that list.

- (void)demoMethod:(int)theCount,...
    // ...
    // your code goes here...

To access the arguments list, first declare a local variable of type va_list.

va_list        a_list;

Next, call the va_start() function, passing the va_list local and the last input argument. This sets the va_list local to the contents of the variable list.

va_start(a_list, theCount);

Send the raise:format:arguments: message to the NSException class. Make sure to supply a format string that sees some, if not all, of the input arguments.

Pages: 1, 2, 3, 4, 5, 6, 7

Next Pagearrow