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
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:
1 2 3 4 5 6 7 8
- (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:
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.
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.