Advertisements

Objective-C Lesson 12: Exception Handling


Because of Objective-C’s dynamic runtime there are cases where errors go unnoticed until runtime. These might include sending messages an object doesn’t respond to, or going out of bounds of an array. Sometimes you may not even catch these errors while you’re testing—for example, say you had an application that allowed users to access a value in an array by entering an index value. If the user entered ’20’ when there are only 5 values in the array, the index would be out of bounds; in your testing, you may never have thought to check such numbers.

Granted, the above example is rather contrived, but you should still try to anticipate such issues and do something about them, so your program doesn’t crash. Resolving these issues generally includes logging them and informing the user—an incorrect value, in the above example, or if it’s an error the user can’t fix (sending the wrong message), notify the user and then gracefully exit.

Runtime issues such as these are typically exceptions. If uncaught, your program will quit. Let’s look at an example.

#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSArray *array = [[NSArray alloc] initWithObject:@"A string"];
	[array nonExistentMethod];
	NSLog(@"Object at index 5 is %@", [array objectAtIndex:5]);
	[array release];
	NSLog(@"No issues!");
	[pool drain];
	return 0;
}

Building the program nets you a warning that your array may not respond to nonExistentMethod (obviously). If we run it, we get a whole bunch of info:

2011-05-02 19:18:59.492 Exceptions[760:707] -[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90
2011-05-02 19:18:59.525 Exceptions[760:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff8f509406 __exceptionPreprocess + 198
	1   libobjc.A.dylib                     0x00007fff941a09ea objc_exception_throw + 43
	2   CoreFoundation                      0x00007fff8f584ade -[NSObject doesNotRecognizeSelector:] + 190
	3   CoreFoundation                      0x00007fff8f4db8a3 ___forwarding___ + 371
	4   CoreFoundation                      0x00007fff8f4d8178 _CF_forwarding_prep_0 + 232
	5   Exceptions                          0x0000000100000e33 main + 195
	6   Exceptions                          0x0000000100000d64 start + 52
	7   ???                                 0x0000000000000003 0x0 + 3
)
terminate called throwing an exception

Wading through that, we see some indications that array does indeed not respond to nonExistentMethod. For education’s sake, we’re going to keep that line, and instead use the @try…@catch() blocks. This is what our program now looks like:

#import 

int main(int argc, char *argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSArray *array = [[NSArray alloc] initWithObject:@"A string"];
    @try {
        [array nonExistentMethod];
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception %@", exception);
    }
	NSLog(@"Object at index 5 is %@", [array objectAtIndex:5]);
	[array release];
	NSLog(@"No issues!");
	[pool drain];
	return 0;
}

The code in the @try block is your usual program code. The code in the @catch block is your error handling code—here, we just log the exception. In fact, the next NSLog will also cause an exception—an out of bounds exception. We can therefore move that line into the @try block.

int main(int argc, char *argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSArray *array = [[NSArray alloc] initWithObject:@"A string"];
    @try {
        [array nonExistentMethod];
        NSLog(@"Object at index 5 is %@", [array objectAtIndex:5]);
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception %@", exception);
    }
	[array release];
	NSLog(@"No issues!");
	[pool drain];
	return 0;
}

The results are now

2011-05-02 19:33:09.308 Exceptions[872:707] -[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90
2011-05-02 19:33:09.312 Exceptions[872:707] Caught exception -[__NSArrayI nonExistentMethod]: unrecognized selector sent to instance 0x100113e90
2011-05-02 19:33:09.313 Exceptions[872:707] No issues!

Note now that our program no longer crashes, but instead catches the exceptions, and continues. Note that the @try…@catch block is exited after the first exception is caught, regardless of if there are any others.

You can also append a @finally { } block that executes regardless of whether there has been an exception or not.

Throwing Exceptions

Unlike in Java, Objective-C exceptions should not be thrown whenever there is an error—try to handle the error, or use something like NSError or the NSAssert() method.

As Apple states, “You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
If you have an existing body of code (such as third-party library) that uses exceptions to handle error conditions, you may use the code as-is in your Cocoa application. But you should ensure that any expected runtime exceptions do not escape from these subsystems and end up in the caller’s code. For example, a parsing library might use exceptions internally to indicate problems and enable a quick exit from a parsing state that could be deeply recursive; however, you should take care to catch such exceptions at the top level of the library and translate them into an appropriate return code or state.”

Throwing exceptions is a rather “expensive” procedure, and of course uncaught exceptions will cause your program to crash. That being said, throwing an exception is straightforward:

NSException* myException = [NSException
        exceptionWithName:@"IndexOutOfBoundsException"
        reason:@"Attempted to access an array index that is out of bounds"
        userInfo:nil];
@throw myException;
// [myException raise]; /* equivalent to throwing the exception, above */

A more popular usage is in the case where you can’t recover from the issue (invalid data, for example, or where you tried to access an index that is beyond the size of an array)—you could catch the exception to perform some cleanup, then throw it back to the system for something else to catch, or to quit at that point.

Exceptions provide a powerful and flexible way to handle issues in your code. Use them wisely—don’t make the runtime system a massive juggling round.

Advertisements
Leave a comment

4 Comments

  1. I would suggest changing the last example from a file-not-found exception to something like an out-of-bounds exception, to avoid implying that missing files are exception-worthy. (File access, of course, is one area where success cannot usually be foreseen on the application author’s end, and so is always accompanied by NSError**-style handling.)

    Reply
  1. Learn Objective-C in 24 Days « Programming for iOS
  2. NS Exception and NSAssert « jamesdevnote

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Advertisements
  • Welcome

    My goal is to make CupsOfCocoa into a beautiful source for beginners to the iPhone platform to get started. Subscribe below for more, and stay tuned!

  • Contact Me

    If you need to contact me for any reason, feel free to send me an email.
  • The Giving Spirit

    If you've found this site helpful, would you consider donating a little sum? Any amount is appreciated...Thanks so much!

  • Roadmap

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 222 other followers

  • Back to the Past

    May 2011
    S M T W T F S
    « Apr   Jun »
    1234567
    891011121314
    15161718192021
    22232425262728
    293031  
  • Time Machine

  • You count!

    • 621,851 views
  • Worldwide Stats

    free counters
  • Advertisements
%d bloggers like this: