NSException
The NSException class supports exception management. Its methods provide functionality for creating NSException instances, querying an instance, raising exceptions, and retrieving exception stack frames. The following statement uses the exceptionWithName:reason:userInfo: factory method to create an NSException object.
NSException *exception = [NSException exceptionWithName:NSGenericException
reason:@"Test exception"
userInfo:nil];
The parameters for this method are the name of the exception (exceptionWithName:), the reason for the exception (reason:), and a dictionary (userInfo:) containing user-defined information relating to the exception. In the preceding example, the exception name is NSGenericException, one of the Foundation Constants general exception names. Listing 14-9 creates and raises an exception using the raise:format: class method. This exception is caught and handled within the body of the corresponding @catch() block.
Listing 14-9. Creating and Raising an NSException Instance
@try
{
[NSException raise:NSGenericException format:@"My Generic Exception"];
}
@catch (NSException *exception)
{
NSLog(@"EXCEPTION\nName-> %@\nDescription-> %@", [exception name],
[exception description]);
}
Listing 14-9 shows the argument to the @catch() directive is an NSException object, the exception raised in the corresponding exception-handling domain. Exceptions can also be nested, thereby enabling an exception to be processed by the local exception handler domain and any surrounding handlers. Listing 14-10 modifies the example shown in Listing 14-9 to demonstrate the use of nested exception handlers.
Listing 14-10. Nested Exception Handlers
@try
{
@try
{
[NSException raise:NSGenericException format:@"My Generic Exception"];
}
@catch (NSException *exception)
{
NSLog(@"EXCEPTION handling in domain 2\nName-> %@\nDescription-> %@",
[exception name], [exception description]);
@throw;
}
@finally
{
NSLog(@"Cleanup for exception domain 2");
}
}
@catch (NSException *exception)
{
NSLog(@"EXCEPTION handling in domain 1\nName-> %@\nDescription-> %@",
[exception name], [exception description]);
}
@finally
{
NSLog(@"Cleanup for exception domain 1");
}
If an exception is not caught by your code, it is caught by the uncaught exception handler function. The default implementation of this function logs a message to the output pane, and then exits the program. The Foundation function NSSetUncaughtExceptionHandler() can be used to set a custom uncaught exception handler.
Exceptions and Memory Management
Memory management must be carefully considered when handling exceptions, particularly when using Manual Retain Release (MRR) memory management. As exception processing changes program flow control, Listing 14-11 demonstrates how an exception in a method (when using MRR memory management) may cause an object to leak memory.
Listing 14-11. Exception Handling and Memory Leaks
- (void) processOrderWithItem:(OrderItem *)item
{
Order *order = [[Order alloc] initWithItem:item];
[order process];
[order release];
}
If the process method throws an exception, the Order object will not be released, and hence it leaks memory. To prevent this, the solution is to enclose the method statements within an exception-handling domain and to release the allocated object within its @finally block, as shown in Listing 14-12.
Listing 14-12. Preventing Memory Leaks with an Exception Handling Domain
- (void) processOrderWithItem:(OrderItem *)item
{
Order *order = nil;
@try
{
order = [[Order alloc] initWithItem:item];
[order process];
[order release];
}
@finally
{
[order release];
}
}
By default, ARC memory management is not exception-safe. A program using ARC can be made exception-safe if it is compiled with the -fobjc-arc-exceptions option. This will increase program resource utilization and also slightly decrease performance, and hence its use must be carefully considered.
Performing Exception Handling
Now you will create a program that demonstrates exception handling for a Foundation Framework object. In Xcode, create a new project by selecting New Project . . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Optionswindow, specify XProcessor for the Product Name, choose Foundation for the Project Type, and select ARC memory management by selecting the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary selectNew Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.
In the Xcode project navigator, select the main.m file and update the main() function, as shown inListing 14-13.
Listing 14-13. XProcessor main( ) Function Implementation
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool
{
NSArray *words = @[@"Hello", @"Bonjour", @"Guten Tag", @"Hola"];
@try
{
int count = 0;
NSLog(@"Salutation = %@", words[count]);
}
@catch (NSException *exception)
{
NSLog(@"EXCEPTION\nName-> %@\nDescription-> %@", [exception name],
[exception description]);
}
}
return 0;
}
The main() function first creates an NSArray object with four entries. Then an entry from the array is retrieved and logged to the output pane. Because the NSArray method for retrieving an object at a specified index will throw an NSRangeException (one of the Foundation standard exception names) if the index is beyond the end of the array, this statement is placed within an exception-handling domain. If an exception is thrown, it is logged to the output pane.
Now save, compile, and run the XProcessor program and observe that the expected message (Hello) is displayed in the output pane (as shown in Figure 14-3).
Figure 14-3. Testing the XProcessor project, no exception raised
No exception was thrown because the value set for the index was within range. Now change the value for the count variable to four.
int index = 0;
Then recompile and run the program. Observe that because the index exceeded the range, an exception was thrown and handled within the @catch() block (as shown in Figure 14-4).
Figure 14-4. Testing the XProcessor project, exception raised
This example demonstrates exception handling for Foundation Framework APIs. Let’s continue on with the Foundation constants that define a set of standard exception names.
Foundation Standard Exception Names
The Foundation Framework defines a set of standard exception names. These general exception names are listed and described in Table 14-2.
Table 14-2. Foundation Constants General Exception Names
Exception Name | Description |
---|---|
NSGenericException | A generic name for an exception. |
NSRangeException | An exception that occurs when attempting access outside the bounds of some data, such as an array or a string. |
NSInvalidArgumentException | An exception that occurs when you pass an invalid argument to a method. |
NSInternalInconsistencyException | An exception that occurs when an internal assertion fails (e.g., via an NSAssert function) and implies an unexpected condition in code. |
NSObjectInaccessibleException | An exception that occurs when a remote object is accessed from a thread that should not access it. |
NSObjectNotAvailableException | A distributed object exception that occurs when the remote side of the NSConnection refuses to send a message to the object because it has not been vended. |
NSDestinationInvalidException | A distributed object exception that occurs when an internal assertion fails. |
NSPortTimeoutException | A timeout set on a port that expires during a send or receive operation. |
NSInvalidSendPortException | The send port of an NSConnection object has become invalid. |
NSInvalidRecievePortException | The receive port of an NSConnection object has become invalid. |
NSPortSendException | An NSPort-specific generic error that occurs when sending a message to a port. |
NSPortReceiveException | An NSPort-specific generic error that occurs when receiving a message from a port. |