A sad iMac

Error reporting wasn’t something Cocoa did in much detail in the early days. There were nil return values instead of objects, or a BOOL that was YES on success.

C didn’t have exceptions, and by the time ObjC got “fake” exceptions based on the longjmp() mechanism, all wrapped up nicely in NSException and NS_DURING/NS_HANDLER/NS_ENDHANDLER macros, most of the framework API had already been written and it would have been too much work to make all of it work with exceptions and adapt all applications to watch for exceptions in error situations. So they were reserved mostly for Distributed Objects (where an additional error return wasn’t possible) and programming errors.

But with the release of Safari and WebKit in 2003, someone at Apple realized they needed more detailed and standardized error information, and introduced the NSError class. NSError is a simple container object for any error code you might encounter on your Apple device, plus a dictionary for any other info like an error message.

Error codes are not unique across all the libraries, frameworks and system services that are on your device, so each error code is also identified by its domain, an arbitrary string constant. For example, Mac OS X offers the NSCocoaErrorDomain, NSOSStatusErrorDomain, NSPOSIXErrorDomain and NSMachErrorDomain error domains, which let you wrap Cocoa framework error codes, Carbon/CoreServices error codes, standard Unix error codes and kernel errors unambiguously, all by wrapping them in an NSError. And you can make up your own for your application, library, and even for a single class. Whatever logical unit of encapsulation makes the most sense.

Most NSErrors are returned as a return parameter. This has the advantage that methods that already have a return value can still be implemented as they were before the arrival of NSError, and can return nil to have an entire chained expression collapse. E.g., a fictional

NSError    theErr = nil;
ULIObject  obj = [[[ULIObject allocGivingError: &theErr] initGivingError: &theErr] autoreleaseGivingError: &theErr];

can fail at any of the three calls and return nil, and none of the subsequent messages will be sent, nor will they touch theErr.

However, one thing Apple tells us is that we shouldn’t look at theErr above unless obj is nil. Why is that? Well, imagine a possible implementation of our fictional autoreleaseGivingError::

-(id) autoreleaseGivingError: (NSError**)outError
{
    ULIAutoreleasePool* currentPool = [ULIAutoreleasePool _currentPoolGivingError: outError];
    if( currentPool == nil )
        currentPool = [NSAutoreleasePool _createBottomPool];
    if( currentPool == nil )    // Still couldn't create?
    {
        // Hand on whatever error _currentPoolGivingError: had.
        return nil;
    }
    
    [currentPool addObject: self];
    return self;
}

Our fictional internal _currentPoolGivingError: method here might return nil and give us an NSError when there is no pool in place yet.

But in the most common case, we will be able to recover from this error by creating the pool 1.

So in most cases, we’ll just create the pool and add the object to it. If callers look at theErr in such a situation, they will see the error object put there by _currentPoolGivingError:, from which we recovered. So they will see an error where none occurred.

And that, kids, is why you always check the return value, and not just the error parameter.

1) This is nonsense in real life, because nobody would ever release this bottom pool and we’d have a quiet leak, but let’s just assume in our example’s world ULIRunLoop will release this bottom pool once it regains control, as part of a lazy-allocation-of-pools scheme.