22

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?

jtbandes
  • 115,675
  • 35
  • 233
  • 266
Max Yankov
  • 12,551
  • 12
  • 67
  • 135

4 Answers4

37

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

jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • That's a very detailed answer, thanks a lot! Sorry for the delay in accepting it — had to learn and try all of it first. – Max Yankov Jul 25 '11 at 13:11
6

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 ...
    }
}
Community
  • 1
  • 1
Wolfgang Schreurs
  • 11,779
  • 7
  • 51
  • 92
  • 5
    Conceptually correct, but the code is seriously wrong. First, you need to check to see if `error` is non-NULL in `objectFromSet:error:` before making the assignment. Secondly, you should **never test the error to check for error**. You **must** check the return value and, *only if nil* do you consider the contents of the error. (Also, there is no requirement to initialize `error` to `nil` in the caller.) – bbum Jul 23 '11 at 19:03
  • 2
    For an example of actual problems with testing the `NSError` out-param, see this answer http://stackoverflow.com/questions/2069039/error-handling-with-nsurlconnection-sendsynchronousrequest/2511161#2511161 (I'm surprised the accepted answer to this question is wrong) – ohhorob Jul 23 '11 at 19:26
  • **@bbum:** Well, didn't realize I never should check for the error first, but instead check for the object if it's not nil, but it does make sense (when looking at the program flow inside the method that returns the error; it will always set the return value to nil in case of errors). So I guess even an experienced iOS developer like me can learn something new every now and then :) – Wolfgang Schreurs Jul 24 '11 at 09:21
0

Objective-C supports exceptions in much the same way as other programming languages, with a similar syntax to Java or C++. As with NSError, exceptions in Cocoa and Cocoa Touch are objects, represented by instances of the NSException class,

You can use

 @try {
        // do something that might throw an exception
    }
    @catch (NSException *exception) {
        // deal with the exception
    }
    @finally {
        // optional block of clean-up code
        // executed whether or not an exception occurred
    }

Show more about Error Handling apple doc .

Toseef Khilji
  • 17,192
  • 12
  • 80
  • 121
0

If a method is supposed to return an object, and it is unable to do so, it should return nil. If there's an error that you want to report to the user so that they can take some kind of action about it, use an NSError object.

NSResponder
  • 16,861
  • 7
  • 32
  • 46
  • To complete this answer: exceptions are only to catch non recoverable mistakes. Read the blockquote at the Exception Programming Topic: http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/Exceptions/Exceptions.html – Jano Jul 23 '11 at 17:49