19
May
2008
 

Cocoa Tutorial: Expanding NSError Usage

by Marcus Zarra

As a follow-up to the previous blog post: Cocoa Tutorial: Using NSError to Great Effect; in this post I am going to demonstrate a few things that can be done with NSError objects that have been received. Specifically, how to add options to an NSError and how to (hopefully) recover from one.

The Responder Chain

The handling of NSError objects follows the responder chain with regard to consuming and manipulating the NSError object. At any level in the responder chain the error can be displayed and/or handled and thereby consumed. If the current object in the responder chain does not handle it (which is common), then it is passed up until it finally reaches the NSApplication object. When it is received by the NSApplication object, that is when the default dialog is presented to the user.

However, that default dialog is not very friendly. There are no options, very little information, and generally nothing that can be done. Fortunately, there are ways to customize this presentation and give the user choices.

Handling an NSError

ErrorDialogWithRecovery.png
To help make this clearer, I am going to demonstrate how to handle an NSError before I demonstrate how to customize the NSError for handling. To get Cococa to give a user more information and to give the user choices, a few more keys need to be added into the NSError object. The proper place to add these additional keys is discussed below.

  • NSLocalizedRecoverySuggestionErrorKey
    This key stores a localized string that will be displayed in the “info” portion of the alert window/sheet that is displayed to the user. This string is meant to display suggestive information to the user to make their choice easier. An example would be “Do you want to try again?”
  • NSRecoveryAttempterErrorKey
    This key points to an object that is to receive the answer to the recovery question. For example, if you have a dialog that has the options “Abort” and “Try Again”, this object would receive the answer to that question. The exact method is discussed below.
  • NSLocalizedRecoveryOptionsErrorKey
    This key points to an array of strings that are the localized button titles which are displayed to the user. To follow the examples above, this key would store an array with two strings: “Try Again” and “Abort”.

The recovery attempt object is very similar to a delegate of the NSError responder chain. When the dialog is presented the recovery attempt object will receive a call once the user makes a choice. Depending on the type of alert (modal vs. sheet), two different methods can be called:

- (BOOL)attemptRecoveryFromError:(NSError*)error 
                     optionIndex:(NSUInteger)recoveryOptionIndex;

- (void)attemptRecoveryFromError:(NSError*)error 
                     optionIndex:(NSUInteger)recoveryOptionIndex 
                        delegate:(id)delegate 
              didRecoverSelector:(SEL)didRecoverSelector 
                     contextInfo:(void*)contextInfo;

The first method returns a BOOL to let the caller know whether or not the error was successfully handled. The second method expects the delegate object to be called. The delegate method should be similar to:

- (void)didPresentErrorWithRecovery:(BOOL)didRecover 
                        contextInfo:(void*)contextInfo;

With the BOOL parameter describing whether or not recovery was successful and the contextInfo being the same object passed in.

Customing the NSError

When the developer is the one who creates the error message, it is pretty easy to prepare it for handling right at creation. However, when the creation point of the NSError is either not the appropriate place to set it up for handling or is in someone else’s code, there are other chances to update the error with handling information.

If you want to capture an NSError and inject the handler information discussed above, then you need to implement a method that will be called as part of the responder chain. If you want to intercept the error at some point before the NSApplication’s delegate, then the following method needs to be implemented:

- (NSError*)willPresentError:(NSError*)error;

However, if you want to manipulate the error at the Application delegate the following method is appropriate:

- (NSError*)application:(NSApplication*)app 
       willPresentError:(NSError*)error;

In this method, you can either return the NSError object that is being passed in, or you can create a new NSError object and return it. In either case, the error chain will use whatever object is returned from this method.

Conclusion

With these methods, it is possible to drastically improve the handling and presentation of errors. With these methods, errors can be reduced to simple decisions for the user to resolve rather than application crashing events.

I have attached the project from the previous NSError article. In addition, that project has been updated to utilize the methods described in this entry.

xcode.png
NSError Tutorial

Comments

sdfisher says:

But don’t use the word Abort. It’s easy for us technical folk to forget these words have real meanings. But we’ve seen people burst into tears when reading a dialog with an Abort button.

Just thought I’d mention, if you use “Cancel” instead of “Abort” the escape and command-period shortcuts will work in your alert.