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.
Handling Error Objects Returned From Methods
Many methods of the Cocoa and Cocoa Touch classes include as their last parameter a direct or indirect reference to an NSError
object. In some Foundation and UIKit methods you find NSError
objects as arguments of delegation methods. The following declaration is from the UIKit framework’s UIWebViewDelegate
protocol; a delegate would implement a method such as this to find out if an operation failed:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error; |
Some methods of the Cocoa frameworks that you call include an indirect reference to an NSError
object; these methods typically perform an operation such as creating a document, writing a file, or loading a URL. For example, the following method declaration is from the NSDocument
class header file:
- (BOOL)writeToURL:(NSURL *)absoluteURL |
ofType:(NSString *)typeName |
error:(NSError **)outError; |
If a method such as this 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 2-1 illustrates this approach.
Listing 2-1 Handling an NSError
object returned from a AppKit 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. |
} |
This code in Listing 2-1 uses the returned NSError
to display an error alert to the user immediately. (UIAlertView
, the UIKit class corresponding to NSAlert
, has no equivalent method for alertWithError:
.) Error objects in the Cocoa domain are always localized and ready to present to users, so they can often be presented without further evaluation.
Instead of merely displaying an error message based on an NSError
object, you could examine the object to determine if you can do something else. For example, you might be able to perform the operation again in a slightly different way that circumvents the error; if you do this, however, you should first request the user’s permission to perform the modified operation.
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 (such as the NSURLErrorDomain
domain), 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 (NSPOSIXErrorDomain
).
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.
Error-Handling Alternatives in OS X
If you are developing a Mac app, there are many other things you can do upon receiving an NSError
object:
If you know how to recover from the error, but require the user’s approval, you could create a new version of the error object that adds a recovery attempter to it (see Recovering From Errors).
Send the error object up the error-responder chain so that other objects in the application can add to it or try to recover from the error.
Before doing this, you might be able supplement the information in the error from the current programming context and then create a new error object that contains this enriched information.
If you use the returned
NSError
object as the basis of a new error object, either by adding a recovery attempter or supplementary information, you can either:Display the message immediately.
Pass the error on to the next error responder.
For more on customizing errors passed up the error-responder chain, see Handling Received Errors.
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 the tiles and message text of an NSAlert
, UIAlertView
, or UIActionSheet
object (or an OS X modal document sheet). This universal approach gives you a large degree of control over the content and presentation of the error alert.
For example, the code in Listing 2-2 composes the message text of an UIAlertView
object from the localized description and failure reason taken from the passed-in NSError
object.
Listing 2-2 Displaying an alert composed mostly from error-object attributes
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { |
NSString *titleString = @"Error Loading Page"; |
NSString *messageString = [error localizedDescription]; |
NSString *moreString = [error localizedFailureReason] ? |
[error localizedFailureReason] : |
NSLocalizedString(@"Try typing the URL again.", nil); |
messageString = [NSString stringWithFormat:@"%@. %@", messageString, moreString]; |
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:titleString |
message:messageString delegate:self |
cancelButtonTitle:@"Cancel" otherButtonTitles:nil]; |
[alertView show]; |
} |
However, you don’t have to use the localized error strings made available by the frameworks. For example, if you think they’re not descriptive enough or want to supplement them with context-specific information, you can identify the error by its domain and code and then substitute your own string values. Take the delegate method used in the prior example; the error object passed to the delegate in webView:didFailLoadWithError:
is almost always of the NSURLErrorDomain
domain. You could then find out which code of this domain is associated with the error and substitute your own string for the strings contained in the error object. (NSURLErrorDomain
and its codes are declared in NSURLError.h
.) Listing 2-3 gives an example of this.
Listing 2-3 Assigning custom message strings based on error domain and code
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { |
NSString *errorMsg; |
if ([[error domain] isEqualToString:NSURLErrorDomain]) { |
switch ([error code]) { |
case NSURLErrorCannotFindHost: |
errorMsg = NSLocalizedString(@"Cannot find specified host. Retype URL.", nil); |
break; |
case NSURLErrorCannotConnectToHost: |
errorMsg = NSLocalizedString(@"Cannot connect to specified host. Server may be down.", nil); |
break; |
case NSURLErrorNotConnectedToInternet: |
errorMsg = NSLocalizedString(@"Cannot connect to the internet. Service may not be available.", nil); |
break; |
default: |
errorMsg = [error localizedDescription]; |
break; |
} |
} else { |
errorMsg = [error localizedDescription]; |
} |
UIAlertView *av = [[UIAlertView alloc] initWithTitle: |
NSLocalizedString(@"Error Loading Page", nil) |
message:errorMsg delegate:self |
cancelButtonTitle:@"Cancel" otherButtonTitles:nil]; |
[av show]; |
} |
Propagating Errors for Display by the Application Object (OS X)
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 global 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 object 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 2-4 illustrates how you might invoke the document-modal presentError:modalForWindow:delegate:didPresentSelector:contextInfo:
method.
Listing 2-4 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 2-5 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 2-5 Modal delegate handling the user response
- (void)didPresentErrorWithRecovery:(BOOL)recover contextInfo:(void *)info { |
if (recover == NO) { // Recovery did not succeed, or no recovery attempted. |
// 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 2-6 Directly displaying an error alert dialog
NSAlert *theAlert = [NSAlert alertWithError:theError]; |
NSInteger button = [theAlert runModal]; |
if (button != NSAlertFirstButtonReturn) { |
// handle |
} |
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.
Listing 2-7 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 2-7 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) { |
if (anError != NULL) { |
NSString *description; |
NSDictionary *uDict; |
int errCode; |
if (errno == ENOENT) { |
description = NSLocalizedString(@"No file or directory at requested location", @""); |
errCode = MyCustomNoFileError; |
} else if (errno == EIO) { |
// Continue for each possible POSIX error... |
} |
// Create the underlying error. |
NSError *underlyingError = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain |
code:errno userInfo:nil]; |
// Create and return the custom domain error. |
NSDictionary *errorDictionary = @{ NSLocalizedDescriptionKey : description, |
NSUnderlyingErrorKey : underlyingError, NSFilePathErrorKey : path }; |
*anError = [[NSError alloc] initWithDomain:MyCustomErrorDomain |
code:errCode userInfo:errorDictionary]; |
} |
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 2-7 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 a 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 error objects and exception objects in Cocoa and Cocoa Touch, 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 is required.
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—the global application object itself—to deal with these situations.
Copyright © 2005, 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-01-07