I'm new to Objective-C, and I see that there are different conventions used about error handling. There are exceptions, but also there are situations where functions are just supposed to return nil in case of something going wrong.
So, how do I decide when use which, and how to handle exceptions and unexpected return values? What are the best practices and red flags?
The exception handling mechanisms available to Objective-C programs are effective ways of dealing with exceptional conditions. They decouple the detection and handling of these conditions and automate the propagation of the exception from the point of detection to the point of handling.
The core attributes of an NSError object are an error domain (represented by a string), a domain-specific error code and a user info dictionary containing application specific information.
I won't be definitive about which to use, but here's some info about each of the options:
Exceptions in Obj-C are not really meant to be used to control program flow. From the documentation on exception handling:
The general pattern is that exceptions are reserved for programmer error only, and the program catching such an exception should quit soon afterwards.
For this reason I wouldn't recommend using exceptions @try
/@catch
just to test whether a method worked correctly.
You also have several options for handling exceptions, in addition to setting a higher-level uncaught exception handler.
Errors are typically used in three ways:
An object can simply pass an NSError to its delegate in a designated error-handling callback:
- (void)myObject:(MyObject *)obj didFailWithError:(NSError *)error;
The delegate is then free to take any appropriate action, including perhaps displaying a message to the user. This pattern is commonly used in asynchronous delegate-based APIs.
These are most commonly used in conjunction with a boolean return value: if the return value is NO
, then the NSError object can be examined for more information about the error.
- (BOOL)performTaskWithParameter:(id)param returningError:(out NSError **)error;
Where one possible usage pattern would be:
NSError *error; if (![myObject performTaskWithParameter:@"param" returningError:&error]) { NSLog(@"Task failed with error: %@", error); }
(Some people also prefer to store the boolean result in a variable before checking it, such as BOOL success = [myObject perform...];
.) Due to the linear nature of this pattern, it's best used for synchronous tasks.
A fairly recent pattern since the introduction of blocks, yet a quite useful one:
- (void)performAsynchronousTaskWithCompletionHandler:(void (^)(BOOL success, NSError *error))handler;
Used like this:
[myObject performAsynchronousTaskWithCompletionHandler:^(BOOL success, NSError *error) { if (!success) { // ... } }];
This varies a lot: sometimes you won't see the boolean parameter, just the error; sometimes the handler block has no arguments passed to it and you just check a state property of the object (for example, this is how AVAssetExportSession works). This pattern is also great for asynchronous tasks, when you want a block-based approach.
Cocoa on Mac OS X has a quite thorough error-handling path. There is also NSAlert's convenience method + (NSAlert *)alertWithError:(NSError *)error;
. On iOS, the NSError class still exists, but there aren't the same convenience methods to handle errors. You may have to do a lot of it yourself.
Read the Error Handling Programming Guide for more information.
This is often used in conjunction with NSError out parameters; for example, NSData's method
+ (id)dataWithContentsOfFile:(NSString *)path options:(NSDataReadingOptions)mask error:(NSError **)errorPtr;
If reading the file fails, this method returns nil
, and further information is stored in an error.
One reason this is a particularly convenient pattern is because of nil messaging, which can be done safely with no effect in Obj-C. I won't go into detail here on why this is useful, but you can read more about it elsewhere on the interwebs. (Just make sure to find an up-to-date article; it used to be that methods returning floating-point values wouldn't necessarily return 0 when sent to nil, but now they do, as described in the documentation.)
Exceptions should be used as little as possible in Objective-C. Where other languages would use exceptions, in Objective-C it's recommended to make use of NSError objects most of the time.
Apple's documentation on exception handling is here: http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/Exceptions/Exceptions.html%23//apple_ref/doc/uid/10000012il
So how would one make use of NSError objects? Well, if we look at Apple's classes, errors are returned using an indirection pointer.
For example:
- (NSObject *)objectFromSet:(NSSet *)set error:(NSError **)error { // get an object from a set; if the set has at least 1 object // we return an object, otherwise an error is returned. NSObject *object = [set anyObject] if (!object) { *error = [NSError errorWithDomain:@"AppDomain" code:1000 userInfo:nil]; return nil; } return object; } // and then we use the function like this - (void)test { NSError *error = nil; NSSet *set = [[[NSSet alloc] init] autorelease]; NSObject *object = [self objectFromSet:set error:&error]; if (object) { // use the object, all went fine ... } else { // handle error, perhaps show an alert view ... } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With