What are the best practices for exceptions / returning NO / nil in Objective-C?

advertisements

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?


I won't be definitive about which to use, but here's some info about each of the options:

Exceptions

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

Errors are typically used in three ways:

Delegate methods

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.

Out parameters

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.

Block-based completion handlers

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.

Handling errors

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.

Returning nil

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