< Previous PageNext Page > Hide TOC

Using and Creating Error Objects

The following sections describe how to deal with NSError objects returned from framework methods, how to display error messages using error objects, how to create error objects, and how to implement methods that return error objects by reference.

In this section:

Handling Error Objects Returned From Methods
Displaying Information From Error Objects
Creating and Returning NSError Objects


Handling Error Objects Returned From Methods

Beginning with Mac OS X v10.4, many methods in the Cocoa frameworks include as their last parameter an indirect reference to an NSError object. Typically these are methods that create a document, write a file, load a URL, access a resource, or perform a similar operation. For example, the following method declaration is from the NSDocument class header file:

- (BOOL)writeToURL:(NSURL *)absoluteURL
    ofType:(NSString *)typeName
    error:(NSError **)outError;

If this method encounters an error in its implementation, it directly returns NO to indicate failure and indirectly returns (if the client code requests it) an NSError object in the last parameter to describe the error. If you want to evaluate the error, declare an NSError object variable before calling a method such as writeToURL:ofType:error:. When you invoke the method, pass in a pointer to this variable. (If you are not interested in the error, just pass NULL.) If the method directly returns nil or NO, inspect the NSError object to determine the cause of the error or simply display an error alert. Listing 3-1 illustrates this approach.

Listing 3-1  Handling an NSError object returned from a Cocoa method

NSError *theError;
BOOL success = [myDoc writeToURL:[self docURL]
                    ofType:@"html"
                    error:&theError];
if (success == NO) {
    // maybe try to determine cause of error and recover first
    NSAlert *theAlert = [NSAlert alertWithError:theError];
    [theAlert runModal]; // ignore return value
}

Note: Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO.

This code fragment uses the returned NSError to display an error alert to the user immediately. Error objects in the Cocoa domain are always localized and ready to present to users, so they can often be presented without further evaluation. (The example in Listing 3-1 is just one of the several approaches you could take for displaying errors; see the following section, “Displaying Information From Error Objects,” for more on this topic).

Instead of merely displaying an error message returned from a framework call, you could examine the NSError object to determine if you can do something else:

Important: You should always special-case test for the NSUserCancelledError error code (in the NSCocoaErrorDomain). This code indicates that the user cancelled the operation (for example, by pressing Command-period). In this case, you should not display any error dialog.

When evaluating an NSError object, always use the object’s domain and error code as the bases of tests and not the strings describing the error or how to recover from it. Strings are typically localized and are thus likely to vary. With a few exceptions, pre-existing errors returned from Cocoa framework methods are always in the NSCocoaErrorDomain domain; however, because there are exceptions you might want to test whether the top-level error belongs to that domain. Error objects returned from Cocoa methods can often contain underlying error objects representing errors returned by lower subsystems, such as the BSD layer (NSPOSXIErrorDomain).

Of course, to make a successful evaluation of an error, you have to anticipate the errors that might be returned from a method invocation. And you should ensure that your code deals adequately with new errors that might be returned in the future.

Note: Some methods of the Cocoa frameworks can give you error objects in ways other than indirection. For example, some methods of the NSXMLParser class, such as parser:validationErrorOccurred:, pass an NSError object to the delegate if an fatal error has occurred. Other methods, such as streamError of the NSStream class, return an NSError object directly. What you can do with the error object is the same in all cases.

Displaying Information From Error Objects

There are several different ways to display the information in NSError objects. You could extract the localized description (or failure reason), recovery suggestion, and the recovery options from the error object and use them to initialize an NSAlert object with message text, informative text, and button titles. Although this approach is the most painstaking, it does give you a large degree of control over the content and presentation of the error alert.

Fortunately, the Application Kit provides a few shortcuts for displaying error alerts. The presentError: and the presentError:modalForWindow:delegate:didPresentSelector:contextInfo: methods permit you to originate an error alert that is eventually displayed by the application object, NSApp; the former method requests an application-modal alert and the latter a document-modal alert. You must send either of these present-error messages to an objects in the error-responder chain (see “The Error-Responder Chain”): a view object, a window object, an NSDocument object, an NSWindowController object, an NSDocumentController object, or NSApp. (If you send the message to a view, it should ideally be a view object associated in some way with the condition that produced the error.) Listing 3-2 illustrates how you might invoke the document-modal presentError:modalForWindow:delegate:didPresentSelector:contextInfo: method.

Listing 3-2  Displaying a document-modal error alert

NSError *theError;
NSData *theData = [doc dataOfType:@"xml" error:&theError];
if (!theData && theError)
    [anyView presentError:theError
            modalForWindow:[doc windowForSheet]
            delegate:self
            didPresentSelector:
                @selector(didPresentErrorWithRecovery:contextInfo:)
            contextInfo:nil];

After the user dismisses the alert, NSApp invokes a method (identified in the didPresentSelector: keyword) implemented by the modal delegate. As Listing 3-3 shows, the modal delegate in this method checks whether the recovery-attempter object (if any) managed to recover from the error and responds accordingly.

Listing 3-3  Modal delegate handling the user response

- (void)didPresentErrorWithRecovery:(BOOL)recover
            contextInfo:(void *)info {
    if (recover == NO) { // recovery did not succeed, or no recovery attempter
        // proceed accordingly
    }
}

For more on the recovery-attempter object, see “Recovering From Errors.”

Sometimes you might not want to send an error object up the error-responder chain to be displayed by NSApp. You would rather show an error alert to the user immediately, and not have to construct it yourself. The NSAlert class provides the alertWithError: method for this purpose.

Listing 3-4  Directly displaying an error alert dialog

NSAlert *theAlert = [NSAlert alertWithError:theError];
int button = [theAlert runModal];
if (button != NSAlertFirstButtonReturn) {
    // handle
}

Note: The presentError: and presentError:modalForWindow:delegate:didPresentSelector:contextInfo: methods silently ignore NSUserCancelledError errors in the NSCocoaErrorDomain domain.

Creating and Returning NSError Objects

You can declare and implement your own methods that indirectly return an NSError object. Methods that are good candidates for NSError parameters are those that open and read files, load resources, parse formatted text, and so on. In general, these methods should not indicate an error through the existence of an NSError object. Instead, they should return NO or nil from the method to indicate that an error occurred. Return the NSError object to describe the error.

If you are going to return an NSError object by reference in an implementation of such a method, you must create the NSError object. You create an error object either by allocating it and then initializing it with the initWithDomain:code:userInfo: method of NSError or by using the class factory method errorWithDomain:code:userInfo: . As the keywords of both methods indicate, you must supply the initializer with a domain (string constant), an error code (a signed integer), and a “user info” dictionary containing descriptive and supporting information. (See “Error Objects, Domains, and Codes” for full descriptions of these data items.) You should ensure that all strings in the user info dictionary are localized. If you create an NSError object with initWithDomain:code:userInfo:, you should send autorelease to it before you return it to the caller.

Listing 3-5 is an example of a method that, for the purpose of illustration, calls the POSIX-layer open function to open a file. If this function returns an error, the method creates an NSError object of the NSPOSIXErrorDomain that is used as the underlying error of a custom error domain returned to the caller.

Listing 3-5  Implementing a method that returns an NSError object

- (NSString *)fooFromPath:(NSString *)path error:(NSError **)anError {
    const char  *fileRep = [path fileSystemRepresentation];
    int fd = open(fileRep, O_RDWR|O_NONBLOCK, 0);
    if (fd == -1) {
        NSString *descrip;
        NSDictionary *uDict;
        int errCode;
        if (errno == ENOENT) {
                descrip = NSLocalizedString(@"No such file or directory at
                        requested location", @"");
                errCode = MyCustomNoFileError;
        } else if (errno == EIO) {
            // continues for each possible POSIX error...
        }
 
        // Make underlyining error
        NSError *undError = [[[NSError alloc] initWithDomain:NSPOSIXErrorDomain
            code:errno userInfo:nil] autorelease];
        // Make and return custom domain error
        NSArray *objArray = [NSArray arrayWithObjects:descrip, undError, path, nil];
        NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey,
            NSUnderlyingErrorKey, NSFilePathErrorKey, nil];
        NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray
            forKeys:keyArray];
        if (anError != NULL)
            *anError = [[[NSError alloc] initWithDomain:MyCustomErrorDomain
                code:errCode userInfo:eDict] autorelease];
        return nil;
    }
    // ...

In this example, the returned error object includes in its user info dictionary the path that caused the error.

As the example in Listing 3-5 shows, you can use errors that originate from underlying subsystems as the basis for error objects that you return to callers. You can use raised exceptions that your code handles in the same way. An NSException object is compatible with an NSError object in that its attributes are a name, a reason, and an user info dictionary. You can easily transfer information in the exception object over to the error object.

A Note on Errors and Exceptions

It is important to keep in mind the difference between NSError objects and NSException objects, and when to use one or the other in your code. They serve different purposes and should not be confused.

Exceptions (represented by NSException objects) are for programming errors, such as an array index that is out of bounds or an invalid method argument. User-level errors (represented by NSError objects) are for runtime errors, such as when a file cannot be found or a string in a certain encoding cannot be read. Conditions giving rise to exceptions are due to programming errors; you should deal with these errors before you ship a product. Runtime errors can always occur, and you should communicate these (via NSError objects) to the user in as much detail as they require.

Although exceptions should ideally be taken care of before deployment, a shipped application can still experience exceptions as a result of some truly exceptional situation such as “out of memory” or “boot volume not available.” It is best to allow the highest level of the application—NSApp itself—to deal with these situations.



< Previous PageNext Page > Hide TOC


© 2005, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-03-04)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.