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

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

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