macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Understanding Exceptions and Handlers in Cocoa
Pages: 1, 2, 3, 4, 5, 6, 7

When Handlers Fail

If an exception handler failed to process an NSException, it should send the exception to the next available handler. But if all handlers fail, the exception becomes an uncaught exception. A special function called the default exception handler gets to trap these exceptions.

The Default Exception Handler

The default exception handler serves as a catchall for all uncaught exceptions. It is located at the end of the exception handling chain. Its aim is to gracefully stop the application in response to the exception.

Most Cocoa software is built around the NSApplication class, which installs its own default exception handler at launch time. When the default handler gets an uncaught exception, it does the following tasks in order.

  • The handler updates the crash log file for the affected software with details on the uncaught application.
  • It sends a terminate message back to NSApplication. NSApplication then starts the shutdown process for the Cocoa software.
  • Once the software shuts down, Mac OS X displays the modal dialog shown in Figure 2. This dialog is displayed only when the default exception handler sends a terminate message.

You can control how the default exception handler behaves by changing one of two global masks. To access these masks, you need to add the NSExceptionHandler framework to your project. First, click on the Frameworks group on the Groups & Files pane of your project window. Choose Add to Project from the Projects menu, and use the dialog to navigate to the /System/Library/Frameworks directory. Select the bundle ExceptionHandling.framework, and click on the OK button to add it to the project. Finally, add the following statement to the source file that will access the masks.

#import <ExceptionHandling/NSExceptionHandler.h>

The Exception Handling Mask

The Exception Handling Mask controls how the default handler reacts to top-level and low-level exceptions. It also selects the error signals that the handler should trap. In fact, the default handler can trap two other signals: system-level exceptions and runtime errors. These signals are defined as follows.

  • Top-level exceptions are those that reach the topmost two exception handlers. They also include exceptions trapped by the default handler.
  • Low-level exceptions are those trapped by handlers located deep within the nested structure.
  • A system-level exception comes from the underlying OS. It is often fatal, requiring the affected application to be shut down or restarted. One example of a system-level exception is illegal memory access. Another example is a stack overflow.
  • A runtime error comes from the runtime library. It can be recoverable, depending on the cause of the error. One example of a runtime-error is releasing an object being deallocated. Another example is sending a message to an object that does not support the message.

Figure 7 shows the bit structure of the mask. Though the mask is 19 bits long, only the lower 9 bits are available for use. The rest are reserved by Apple for future settings.

Bit structure of the exception handling mask.
Figure 7. Bit structure of the exception handling mask

Bits zero to five are the handler bits. They tell the default handler how to react to the each error signal. Three of the bits tell the handler to log the signal. The other three tell the handler to process the signal.

Bits six to nine are the debugging bits. They tell the default handler how react to top-level or low-level exceptions. Again, two of the bits tell the handler to log the exceptions. The other two tell it to process the exceptions.

Assume you want the default handler to log system-level exceptions. You also want it to process runtime errors. Since you use your own handler to trap uncaught exceptions, you want the default handler not to deal with those exceptions.

To change the mask, first send a defaultExceptionHandler message to the NSExceptionHandler class. The class then returns an instance of the default handler. In the example below, the instance is stored in the local variable aHandler.

NSExceptionHandler     *aHandler;
//...
aHandler = [NSExceptionHandler defaultExceptionHandler];

Next, send an exceptionHandlingMask message to the instance. The instance returns the current mask settings as an unsigned integer.

unsigned int *theMask;
//...
theMask = [aHandler exceptionHandlingMask];

Now, check Figure 7 for the desired bit settings. Then change the bit mask as follows:

theMask = NSLogUncaughtSystemExceptionMask | NSHandleUncaughtRuntimeErrorMask;

When done, send a setExceptionHandlingMask: message to the NSExceptionHandler instance. Pass the changed mask as an unsigned integer.

[aHandler setExceptionHandlingMask:theMask];

If you want the default handler to log any top-level exceptions as well, change the bit mask as follows:

theMask = NSLogUncaughtSystemExceptionMask | NSHandleUncaughtRuntimeErrorMask
        | NSLogTopLevelExceptionMask;

Use this new mask to update the default handler as directed earlier.

The Exception Hanging Mask

The Exception Hanging Mask controls how the default handler uses an external debugger. It is 10 bits long, and its bit structure is shown in Figure 8. Only the lower five bits of the mask can be used. The rest are reserved by Apple for future settings. Also, the lower three bits select the error signal. The upper three bits select the exception level.

Bit structure of the exception hanging mask
Figure 8. Bit structure of the exception hanging mask

Assume you want system-level exceptions and top-level exceptions sent to the debugger. First, create an instance of NSExceptionHandler. Then send an exceptionHangingMask message to the instance. The instance returns the current mask settings as an unsigned integer.

unsigned int *theMask;
//...
theMask = [aHandler exceptionHangingMask];

Check Figure 8 for the correct bit settings. Set the bit mask as shown.

theMask = NSHangOnUncaughtSystemExceptionMask | NSHangOnTopLevelExceptionMask;

After making your changes, send a setExceptionHangingMask: message to the instance. Pass the new bit mask as an unsigned integer.

[aHandler setExceptionHangingMask:theMask];

Installing a Default Exception Handler

There are cases where you are unable to use the default handler from NSApplication. One such case is where your application project does not use NSApplication. Another is where your project is a plug-in or class framework. For these cases, Cocoa gives you the means to install your own custom default handler.

In order for your custom handler to work, make sure it conforms to the function prototype defined in the header file, NSException.h. The following shows how that handler should appear. Notice the handler uses a void for a return value. Also, notice that it takes a single NSException as its input argument.

void your_custom_handler(NSException *anException);
{
    //...
    // your handler code goes here
    //...
}

Cocoa provides two global functions to install and retrieve your custom handler. Both functions use function pointers to refer to your handler. Assume you have a custom default handler named aDefaultHandler. To install the handler, use the function NSSetUncaughtExceptionHandler(). Pass a pointer to the handler as the input argument.

NSSetUncaughtExceptionHandler(&aDefaultHandler);

To retrieve the handler, use the function NSGetUncaughtExceptionHandler(). The function returns a pointer to the handler. De-reference the pointer, and pass the NSException object as its input argument.

NSException     *theError;
unsigned int     thePointer;
//...
thePointer = NSGetUncaughtExceptionHandler();
(*thePointer)(theError);

You can also invoke your custom handler directly from the function call itself.

(*NSGetUncaughtExceptionHandler())(theError);

Always install your custom handler at the very start of the software. There are two places where you can do this. One is in the init() method of your main controller, as the following shows.

- (id)init
{
    if (self = [super init])
    {
    //...
    // your init code goes here
    //...
    NSSetUncaughtExceptionHandler(&aDefaultHandler);
    }
    return (self);
}

Another is in the main() function call itself, as shown in the following code example. Make sure to install your custom handler after you initialized the main autorelease pool. Also, make sure to import the NSException.h file into the source file that has the main controller class or the main() function.

int main (int argc, const char * argv[]) 
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    NSSetUncaughtExceptionHandler(&aDefaultHandler);
    //...
    // the rest of your code goes here
    //...
    [pool release];
    return 0;
}

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

Next Pagearrow