Chapter     14

Expert Section: Error Handling

How your code deals with errors is critical to implementing quality software. Runtime errors that impact the operation and/or performance of a program can be due to a variety of causes, such as incorrect user input, system issues, or programming errors. The Foundation Framework includes several APIs for handling runtime error conditions. In this Expert Section chapter, you will examine these APIs in depth. The chapter explores the causes of runtime errors, the programming options for error handling, and the Foundation Framework APIs for handling errors and exception conditions. After completing this chapter, you will be able to use the Foundation Framework error handling and exception processing APIs to write programs that respond properly to errors as they arise, and even provide support for error recovery.

Runtime Error Conditions

A runtime error is an error that occurs while a program is running; it contrasts with other categories of program errors, such as syntax and linking errors, which occur prior to program execution. Runtime errors are generally due to the following types of conditions:

  • Logical error: An error due to the fact that the code doesn’t correctly implement the corresponding program logic. This type of error is manifested during program execution when it doesn’t produce the expected output.
  • Semantic error: An error due to the improper use of program statements (for example, performing division by zero or the improper initialization of a variable).
  • User input error: An error due to invalid user input.

If any of these conditions occurs and is not handled, it can result in a variety of adverse consequences, including program termination or improper operation. As a result, a variety of mechanisms have been developed in computer programming to detect and respond to errors. For Objective-C, the Foundation Framework includes several APIs for error handling, grouped into the following categories:

  • Assertions
  • Error codes
  • Error objects
  • Exceptions

Before examining these APIs in detail, let’s briefly review each of these error types and how they are used.

Assertions

An assertion is used to provide a runtime check of a condition that, if not present, should cause the program to terminate. Assertions can be disabled via a compiler directive; hence, they should only be used to detect programming logic and semantic errors, not runtime errors due to invalid user input, and so forth. In Chapter 13, you learned about the Foundation Framework assertion functions. These support the creation of assertions for both Objective-C methods and functions.

Error Codes

Error codes are unique integer values used to identify and perhaps convey information about a particular error that occurs during program execution. Error codes are a simple mechanism for reporting errors but are limited in the amount of information they can provide for error handling.

Error Objects

Error objects are used to both encapsulate and convey information about runtime errors that users need to know about. An error object is made up of an error code and additional information related to the error, some of which can potentially be used for recovering from the error. Because they are objects, error objects also benefit from the basic properties of OOP (encapsulation, inheritance, subclassing, etc.). The Foundation Framework includes several APIs to support error objects: the NSError class for creating and manipulating error objects, and a set of standard error codes.

Exceptions

In computer programming, an exception is an anomalous, unexpected event that occurs during program execution and requires special processing. Exception handling typically changes program flow, with control being passed to special code designated for this purpose. Exception processing logic varies depending upon the severity of the event, ranging from simple logging to attempting recovery and to program termination. The Foundation Framework includes several APIs to support exception processing: the NSException class for creating and manipulating exceptions, and a set of standard exception names. The Objective-C language includes a set of directives that are used with the Foundation Framework exception APIs to perform exception processing.

NSError

The Foundation Framework NSError class is used to create error objects. The properties of this class are an error code, the error domain, and a dictionary of user information. The class includes methods for creating and initializing error objects, retrieving error properties, getting a localized error description, and facilitating error recovery.

An error domain is a mechanism used to organize error codes according to a system, subsystem, framework, and so forth. Error domains enable you to identify the subsystem, framework, and so forth, that detected the error. They also help prevent naming collisions between error codes, because error codes between different domains can have the same value. The user info dictionary is an NSDictionary instance that holds error information beyond the code and domain. The types of information that can be stored in this dictionary include localized error information and references to supporting objects. The following statement creates an error object using the NSError class factory method errorWithDomain:code:userInfo:.

NSError *err = [NSError errorWithDomain:NSCocoaErrorDomain
                                   code:NSFileNoSuchFileError
                               userInfo:nil];

This particular error would be created if, for example, a file is not found at the path specified. Notice that the user info dictionary is not provided (the userInfo parameter is set to nil). Listing 14-1 creates the same error object, this time with a user info dictionary.

Listing 14-1.  Creating an NSError Object

NSString *desc = NSLocalizedString(@"FileNotFound", @"");
NSDictionary *info = @{NSLocalizedDescriptionKey:desc};
NSError *err = [NSError errorWithDomain:NSCocoaErrorDomain
                                   code:NSFileNoSuchFileError
                               userInfo:info];

Listing 14-1 shows the user info dictionary for this example consists of one entry, the key-value pair for a localized description. The localized string is created using the Foundation NSLocalizedString function. The constant NSLocalizedDescriptionKey is a standard user info dictionary key defined in the NSError class.

The Foundation Framework declares four major error domains:

  • NSMachErrorDomain: OS kernel error codes.
  • NSPOSIXErrorDomain: Error codes derived from standard POSIX-conforming versions of Unix, such as BSD.
  • NSOSStatusErrorDomain: Error codes specific to Apple OS X Core Services and the Carbon framework.
  • NSCocoaErrorDomain: All of the error codes for the Cocoa frameworks (this includes the Foundation Framework and other Objective-C frameworks).

In addition to the major error domains presented here, there are also error domains for frameworks, groups of classes, and even individual classes. The NSError class also enables you to create your own error domain when creating and initializing an NSError object. As mentioned previously, Listing 14-1 uses the constant NSLocalizedDescriptionKey. The NSError class defines a set of common user info dictionary keys that can be used to create the key-value pairs for the user info dictionary. These keys are listed in Table 14-1.

Table 14-1. NSError Standard User Info Dictionary Keys

Key Value Description
NSLocalizedDescriptionKey Localized string representation of the error.
NSFilePathErrorKey The file path of the error.
NSStringEncodingErrorKey An NSNumber object containing the string encoding value.
NSUnderlyingErrorKey The error encountered in an underlying implementation (which caused this error).
NSURLErroKey An NSURL object.
NSLocalizedFailureReasonErroryKey Localized string representation of the reason that caused the error.
NSLocalizedRecoverySuggestionErrorKey Localized recovery suggestion for the error.
NSLocalizedRecoveryOptionsErrorKey NSArray containing the localized titles of buttons for display in an alert panel.
NSRecoveryAttempterErrorKey An object that conforms to the NSErrorRecoveryAttempting protocol.
NSHelpAnchorErrorKey Localized string representation of help information for a help button.
NSURLErrorFailingURLString NSURL object containing the URL that caused the load to fail.
NSURLErrorFailingURLStringErrorKey String for the URL that caused the load to fail.
NSURLErrorFailingURLPeerTrustErrorKey SecTrustRef object representing the state of a failed SSL handshake.

Using Error Objects

OK, so now you know how to create NSError objects, but how do you use them? In general, there are two scenarios for obtaining NSError objects:

  • Delegation: An error object passed as a parameter to a delegation method that you implement.
  • Indirection: An error object retrieved via indirection from a method that your code invokes.

The delegation pattern is a design pattern whereby an object (the delegator) delegates one or more tasks to another delegate object. The tasks are encapsulated in the method(s) of the delegate object. When necessary, the delegator invokes the appropriate method on the delegate object, providing any required parameters. Many Foundation Framework classes implement the delegation pattern to enable custom error handling. The delegating Foundation object invokes a method on a delegate object (custom code that you implement) that includes an error object as a parameter.

The NSURLConnectionDelegate protocol declares a delegation method (connection:didFailWithError:) that returns an error object:

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

This protocol is used for asynchronously loading a URL request via an NSURLConnection object. Your code implements a delegate object that conforms to this protocol and sets it as the delegate of the NSURLConnection object. Your code then loads the URL request asynchronously, and if an error occurs, the delegating (NSURLConnection) object invokes the connection:didFailWithError: method on your delegate object.

A common Objective-C programming convention for methods that return an error object is to make this the last parameter of the method and to specify the type of this parameter as a pointer to an error object pointer (also known as double-indirection). The following example declares a method named getGreeting that has an error object of type NSError as its parameter.

- (NSString *)getGreeting(NSError **error);

This approach enables the called method to modify the pointer the error object points to, and if an error occurs, return an error object specific to the method call.

A return value for the method is required. It is either an object pointer or a Boolean value. Your code invokes such a method by including a reference to an error object as its last parameter, or providing NULL as the last parameter if you don’t need to access the error. After the method is invoked, the result is inspected. If the value is NO or nil, then the error object should be processed; else no error object was returned. Many Foundation classes have methods that return an error object by indirection. In addition, your classes can use this convention to implement methods that return an error object.

Listing 14-2 depicts a class named FileWriter, which declares a method that (indirectly) returns an error object.

Listing 14-2.  FileWriter Interface with Method That Returns an NSError Object

@interface FileWriter : NSObject
+ (BOOL) writeData:(NSData *)data toFile:(NSString *)path error:(NSError **)err;
@end

For your code to call this method on a FileWriter object, it must first declare an NSError object, as shown in Listing 14-3, and then check the return value to see if an error occurred.

Listing 14-3.  Invoking FileWriter Method That Returns an NSError Object

NSError *writeErr;
NSData *greeting = [@"Hello, World" dataUsingEncoding:NSUTF8StringEncoding];
BOOL success = [FileWriter writeData:greeting
                              toFile:NSTemporaryDirectory()
                               error:&writeErr];
if (!success)
{
  // Process error
  ...
}

Now you’ll create a couple of example programs that perform error handling for error objects passed by delegation methods and error objects obtained via indirection.

Handling Delegation Method Errors

Now you’ll implement a program that performs error handling for a delegate method. In Chapter 11, you implemented a program, NetConnector, which demonstrates URL loading using the Foundation Framework NSURLConnection class. Here you’ll update this program to handle errors when loading a URL.

In Xcode, open the NetConnector project by selecting Open image NetConnector.xcodeproj from the Xcode File menu. The source code for the project consists of three files that implement the NetConnector class and the main function. Let’s start by making some updates to the NetConnector class. Select the NetConnector.m file in the navigator pane, and then update the NetConnector implementation (updates in bold), as shown in Listing 14-4.

Listing 14-4.  NetConnector Class Implementation, Updated to Handle Errors

#import "NetConnector.h"

@interface NetConnector()

@property NSURLRequest *request;
@property BOOL isFinished;
@property NSURLConnection *connector;
@property NSMutableData *receivedData;

@end

@implementation NetConnector

- (id) initWithRequest:(NSURLRequest *)request
{
  if ((self = [super init]))
  {
    _request = request;
    _finishedLoading = NO;
    
    // Create URL cache with appropriate in-memory storage
    NSURLCache *URLCache = [[NSURLCache alloc] init];
    [URLCache setMemoryCapacity:CACHE_MEMORY_SIZE];
    [NSURLCache setSharedURLCache:URLCache];
    
    // Create connection and begin downloading data from resource
    _connector = [NSURLConnection connectionWithRequest:request delegate:self];
  }
  return self;
}

- (void) reloadRequest
{
  self.finishedLoading = NO;
  self.connector = [NSURLConnection connectionWithRequest:self.request
                                                 delegate:self];
}

#pragma mark -
#pragma mark Delegate methods

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
  NSString *description = [error localizedDescription];
  NSString *domain = [error domain];
  NSInteger code = [error code];
  NSDictionary *info = [error userInfo];
  NSURL *failedUrl = (NSURL *)[info objectForKey:NSURLErrorFailingURLErrorKey];
  NSLog(@" *** ERROR *** Description-> %@ URL-> %@ Domain-> %@ Code-> %li",
        description, failedUrl, domain, code);
  self.finishedLoading = YES;
}

- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
  if (self.receivedData != nil)
  {
    [self.receivedData setLength:0];
  }
}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
  NSURLResponse *response = [cachedResponse response];
  NSURL *url = [response URL];
  if ([[url scheme] isEqualTo:HTTP_SCHEME])
  {
    NSLog(@"Downloaded data, caching response");
    return cachedResponse;
  }
  else
  {
    NSLog(@"Downloaded data, not caching response");
    return nil;
  }
}

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
   if (self.receivedData != nil)
   {
     [self.receivedData appendData:data];
   }
   else
   {
     self.receivedData = [[NSMutableData alloc] initWithData:data];
   }
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
  NSUInteger length = [self.receivedData length];
  NSLog(@"Downloaded %lu bytes from request %@", length, self.request);
  
  // Loaded data, set flag to exit run loop
  self.finishedLoading = YES;
}

@end

An NSURLConnection sends the connection:didFailWithError: message to its delegate object if the connection doesn’t load its request successfully. As shown in Listing 14-4, the NetConnector class is set as the delegate for its NSURLConnection object, and hence its connection:didFailWithError: method will be invoked on request load errors. The method implementation retrieves the properties of the NSError object: description, domain, code, and user info. The URL of the failed load request is stored in the user info dictionary. Its key is NSURLErrorFailingURLErrorKey, one of the NSError standard user info dictionary keys listed in Table 14-1. The method logs the values of this data to the output pane.

Next, you will update the main() function, but before you do that, you need to create a page with a valid URL that cannot be loaded. Doing this forces an error to occur when you attempt to load the page using an NSURLConnection object. Open an OS X terminal window and enter the Unix commands shown in Listing 14-5.

Listing 14-5.  Using the Mac OS X Terminal Utility to Create a File

touch /tmp/ProtectedPage.html
chmod u-r /tmp/ProtectedPage.html

The touch command creates a new, empty file. As shown in Listing 14-6, the full path for the file is /tmp/ProtectedPage.html. The chmod u-r command removes read access to the file for the current user. The corresponding URL for this resource is file:///tmp/ProtectedPage.html. OK, now that this resource is configured properly, let’s update the main() function. Select the main.m file in the navigator pane, and then update the main() function, as shown in Listing 14-6.

Listing 14-6.  NetConnector main( ) Function Implementation

#import <Foundation/Foundation.h>
#import "NetConnector.h"

#define INDEX_URL       @"file:///tmp/ProtectedPage.html"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Retrieve the current run loop for the connection
    NSRunLoop *loop = [NSRunLoop currentRunLoop];

    // Create the request with specified cache policy, then begin downloading!
    NSURLRequest *request = [NSURLRequest
                             requestWithURL:[NSURL URLWithString:INDEX_URL]
                                cachePolicy:NSURLRequestReturnCacheDataElseLoad
                            timeoutInterval:5];
    NetConnector *netConnect = [[NetConnector alloc] initWithRequest:request];
    
    // Loop until finished loading the resource
    while (!netConnect.finishedLoading &&
           [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    
    // Zero out cache
    [[NSURLCache sharedURLCache] removeAllCachedResponses];
  }
  return 0;
}

The key change in the main() function is the URL. It is updated to the URL for the resource that you created earlier, file:///tmp/ProtectedPage.html. The code will attempt to asynchronously load this resource (with an NSURLConnection object) using the NetConnector initWithRequest: method. As shown in Listing 14-4, the NetConnector class implements the connection:didFailWithError: method to handle errors loading a URL.

Now save, compile, and run the updated NetConnector program and observe the messages in the output pane (as shown in Figure 14-1).

9781430250500_Fig14-01.jpg

Figure 14-1. Testing the NSError portion of the updated NetConnector project

The messages in the output pane show that the NSURLConnection failed to load the URL, and hence sent the connection:didFailWithError: message to its delegate object—in this case, the NetConnector instance. As shown in the method implementation of Listing 14-4, the error description, failed URL, error code, and domain are logged to the output pane. OK, great. Now that you’ve got that under your belt, let’s implement a program that returns an error object via indirection.

Creating Errors Objects via Indirection

Now you will create a program that demonstrates error 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 Options window, specify FMErrorObject 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 select New 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 in Listing 14-7.

Listing 14-7.  FMErrorObject main( ) Function Implementation

#import <Foundation/Foundation.h>

#define FILE_PATH       @"/tmp/NoSuchFile.txt"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    NSError *fileErr;
    BOOL success = [fileMgr removeItemAtPath:FILE_PATH error:&fileErr];
    if (!success)
    {
      NSString *description = [fileErr localizedDescription];
      NSString *domain = [fileErr domain];
      NSInteger code = [fileErr code];
      NSDictionary *info = [fileErr userInfo];
      NSURL *failedPath = (NSURL *)[info objectForKey:NSFilePathErrorKey];
      NSLog(@" *** ERROR *** Description-> %@ Path-> %@ Domain-> %@ Code-> %li",
            description, failedPath, domain, code);
    }
  }
  return 0;
}

Logically, the code uses an NSFileManager instance method to remove a file from the file system. If an error occurs when invoking the method, it returns an error object via indirection that describes the error, along with an appropriate return result. As shown in Listing 14-7, the file begins by defining a variable, FILE_PATH, which represents the full path of a file on the local file system (in order to test error object creation and processing make sure that this file, /tmp/NoSuchFile.txt, doesn’t exist). The main() function begins by creating a FileManager object and an NSError pointer. It then attempts to delete the file by invoking the NSFileManager removeItemAtPath:error: method. As the error parameter is not NULL, an NSError object will be returned if an error occurs when invoking this method.

Next, a conditional expression is performed using the Boolean result of this method; if the returned value is NO, then an error occurred trying to remove the file and the body of the conditional expression is executed. This code retrieves the properties of the NSError object: description, domain, code, and user info. The full path of the file that couldn’t be removed is stored in the user info dictionary. Its key is NSFilePathErrorKey, one of the NSError standard user info dictionary keys listed in Table 14-1. The method logs the values of this data to the output pane.

Now save, compile, and run the FMErrorObject program and observe the messages in the output pane (as shown in Figure 14-2).

9781430250500_Fig14-02.jpg

Figure 14-2. Testing NSError by indirection in the FMErrorObject project

The messages in the output pane show that the NSFileManager object failed to remove the file, and hence set an error object (via indirection) in the error parameter of the removeFileWithPath:error: method, and set its return result to NO. The error description, file path, error code, and domain are logged to the output pane. Perfect. Now that you understand how to retrieve and process error objects, let’s examine the Foundation Framework support for error recovery.

Error Recovery

The NSError class provides a mechanism for recovering from errors. The NSErrorRecoveryAttempting informal protocol provides methods that are implemented to perform error recovery. An object that adopts this protocol must implement at least one of its two methods for attempting recovery from errors. The user info dictionary of an NSError object that supports error recovery must contain, at a minimum, the following three entries:

  • The recovery attempter object (retrieved using the key NSRecoveryAttempterErrorKey)
  • The recovery options (retrieved using the key NSLocalizedRecoveryOptionsErrorKey)
  • A localized recovery suggestion string (retrieved using the key NSLocalizedRecoverySuggestionErrorKey)

The recovery attempter may implement any logic appropriate for error recovery. Note that the error recovery functionality is only available on the Apple OS X platform.

Error Responders

The Application Kit provides APIs and mechanisms that can be used to respond to errors encapsulated in NSError objects. The NSResponder class defines an error responder chain used to pass events and action messages up the view hierarchy. It includes methods to display information in the associated NSError object, and then forwards the error message to the next responder. This enables each object in the hierarchy to handle the error appropriately, perhaps by adding additional information pertaining to the error. Note that the error responder functionality is available only on the Apple OS X platform.

NSError Codes

The Foundation Framework defines a number of standard NSError codes as Foundation constants. These errors are enumerations defined for the following NSError class error domains, as follows:

  • Cocoa (NSErrorCocoaDomain)
  • URL Loading System (NSURLDomain)

Exception Processing

Objective-C provides mechanisms for handling exception conditions during program execution. An exception condition can be defined as an unrecoverable programming or runtime error. Examples include errors such as an unimplemented (abstract) method, or runtime errors such as an out-of-bounds collection access. The compiler directives @try, @catch(), @throw, and @finally provide runtime support for exception handling, and the NSException class encapsulates information pertinent to an exception.

When an exception is raised, program control flow transfers to the local exception handler. The program stack frame is also unwound between where the exception was raised and where it was caught and handled. As a result, resources that are not automatically managed (e.g., Foundation Framework objects, objects created using manual reference counting, and any C language–specific resources, such as structures) may not be cleaned up properly. Specifically, the Foundation Framework APIs are not exception-safe; thus if used within an exception-handling domain and an exception is thrown, they may leak memory and/or have corrupted content. Thus, the general rule is that when an exception is raised, no attempt at recovery should be made and the application should be exited promptly.

The @try, @catch, and @finally directives make up a control structure for code that executes within the boundaries of the exception handling logic. The @try directive defines a statement block (also referred to as an exception-handling domain) that can potentially throw an exception. The @catch() directive defines a statement block containing code for handling an exception thrown within the preceding @try block. The parameter of the @catch() directive is the exception object thrown locally, usually an NSException object. A @finally directive defines a statement block (after the preceding @try block) that is subsequently executed whether or not the associated exception is thrown. The @finally block is commonly used to perform any cleanup actions for its corresponding @try and @catch() blocks (e.g., releasing resources, etc.). The syntax for code that uses the exception compiler directives is depicted in Listing 14-8.

Listing 14-8.  Exception Handling Using Compiler Directives

@try
{
  // Code implementing solution logic (may throw exceptions)
  ...
}
@catch (NSException *exception)
{
  // Exception handling code
  ...
}
@finally
{
  // Cleanup code
  ...
}

The @throw directive is used to throw an exception, usually an NSException object but may, in fact, be other types of objects. When an exception is thrown within the body of a @try block, the program jumps immediately to the nearest @catch() block to handle the exception (if one exists).

The Cocoa frameworks come with a large set of predefined exception names that describe specific error states. These should be used (where applicable) when creating an exception.

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 Name-> %@ Description-> %@", [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 Name-> %@ Description-> %@",
          [exception name], [exception description]);
    @throw;
  }
  @finally
  {
    NSLog(@"Cleanup for exception domain 2");
  }
}
@catch (NSException *exception)
{
  NSLog(@"EXCEPTION handling in domain 1 Name-> %@ Description-> %@",
        [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 Options window, 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 select New 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 in Listing 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 Name-> %@ Description-> %@", [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).

9781430250500_Fig14-03.jpg

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).

9781430250500_Fig14-04.jpg

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.

Error Handling Guidelines

You now have a good understanding of error objects and exceptions, along with the corresponding Foundation Framework APIs. So now you may be wondering, “Which do I use for detecting and handling errors?” Before I provide guidelines and recommendations, let’s begin by reviewing some of the key points regarding error objects and exceptions.

Error Objects

  • Pros:
    • Encapsulates error info (code, domain, user info)
    • Provides infrastructure for recovery
    • Supported by the Cocoa frameworks
  • Cons:
    • Couples error-handling logic with business logic
    • Labor intensive, difficult to understand
    • Difficult to maintain and/or modify

Exceptions

  • Pros:
    • Decouples detection and handling of exception conditions, thereby improving program design and maintenance.
    • Automates propagation from the point of detection to handling.
  • Cons:
    • Unwinds stack, resulting in a performance hit.
    • Uses more resources (slightly increases the size of the executable).
    • Still requires parameter/result checks.
    • Cocoa frameworks (including the Foundation Framework) are not exception-safe.

Guidelines and Recommendations

Error objects should be used to detect and handle expected errors within an Objective-C program that can (potentially) be recovered from. Assertions should be used as a programming aid to check program invariants, conditions, assumptions, and so forth. Note that because assertions may be disabled prior to compilation (as is often the case for production releases), they should not be used for parameter checking of public APIs. Exceptions should only be used to detect programming or unexpected runtime errors that cannot be recovered from, and which should be handled by immediately terminating the application.

Roundup

In this chapter, you examined the Foundation Framework APIs for error handling and exception processing. You should now be familiar with Foundation Framework classes that provide the following functionality:

  • Error handling using error objects
  • Exception processing
  • Guidelines for using assertions, error objects, and exceptions

Now that you have concluded this review of the Foundation Framework APIs, it’s time to move on to Part 4 of this book, which focuses on new and advanced topics in Objective-C. Before you do that, however, perhaps now’s a good time to review what you’ve covered so far and then give yourself a well-deserved break. When you’re ready, turn the page and dive in!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.170.134