In Figure 6 are the two methods declared by the protocol. The first method attemptRecovery:optionIndex: handles recovery from an application-wide error. The second method attemptRecovery:optionIndex:delegate:... handles document-specific errors. Both methods get the same two input arguments: the NSError object, and the ID of the clicked dialog button. The method attemptRecovery:optionIndex:delegate:... gets three more inputs: the delegate object assigned to run after recovery, a method from the delegate, and a generic pointer to that method's arguments block.
Figure 6: Two methods from the recovery attempt protocol.
Listing Seven shows the basic structure of a simple recovery class. This sample class, FooRecover, declares two recovery IDs, one for each dialog button, grouping them into the enum constant FooRecoveryType (lines 4-8). Then it implements the protocol method attemptRecoveryFromError:optionIndex:. The method starts by identifying the error object (line 28). If the error is NSFileReadUnknownError, FooRecover invokes the right recovery task with a switch block (lines 33-48). If the error is NSUserCancelledError, FooRecover uses another set of tasks in a second switch block(lines 51-66). FooRecover returns a YES to signal a successful recovery. For all other errors, it returns a NO (line 69).
Listing Seven
// -- FooRecover.h
#import <Cocoa/Cocoa.h>
enum FooRecoveryType;
{
FooRecoverRedo,
FooRecoverReset
};
@interface FooRecover : NSObject
{
// RESERVED...
}
// RESERVED...
@end
// -- FooRecover.m
#import "FooRecover.h"
@implementation FooRecover
// Recover from an application-level error
- (BOOL)attemptRecoveryFromError:(NSError *)anErr
optionIndex:(NSUInteger)anIdx
{
NSInteger *eID;
// identify the error
eID = [anErr code];
switch (eID)
{
case NSFileReadUnknownError:
// identify the chosen recovery task
switch (anIdx)
{
case FooRecoverRedo:
// -- error:recovery:task:redo
// repeat the affected task
// ...
break;
case FooRecoverReset:
// -- error:recovery:task:defaults
// reset the affected task
// ...
break;
default:
// -- RESERVED
break;
}
case NSUserCancelledError:
// identify the chosen recovery task
switch (anIdx)
{
case FooRecoverRedo:
// -- error:recovery:task:redo
// repeat the affected task
// ...
break;
case FooRecoverReset:
// -- error:recovery:task:reset
// reset the affected task
// ...
break;
default:
// -- RESERVED
break;
}
default:
// -- unsupported error
return (NO);
}
// return the recovery result
return (YES);
}
@end
Listing Eight shows how to add a recovery object to the NSError instance. After the snippet defines the localized strings (lines 16-22), it creates two NSString objects for the button labels (lines 25-29). It stores the string objects into an NSArray object (eTask) and stores the array under the key NSLocalizedRecoveryOptionsErrorKey (lines 31-32). Next, the snippet creates an instance of FooRecover (line 34). It stores that recovery object under the key NSRecoveryAttempterErrorKey (lines 35-36).
The error object is then dispatched into the responder chain as described earlier.
Listing Eight
// -- FooRecover.h
#import <Cocoa/Cocoa.h>
enum FooRecoveryType;
{
FooRecoverRedo,
FooRecoverReset
};
@interface FooRecover : NSObject
{
// RESERVED...
}
// RESERVED...
@end
// -- FooRecover.m
#import "FooRecover.h"
@implementation FooRecover
// Recover from an application-level error
- (BOOL)attemptRecoveryFromError:(NSError *)anErr
optionIndex:(NSUInteger)anIdx
{
NSInteger *eID;
// identify the error
eID = [anErr code];
switch (eID)
{
case NSFileReadUnknownError:
// identify the chosen recovery task
switch (anIdx)
{
case FooRecoverRedo:
// -- error:recovery:task:redo
// repeat the affected task
// ...
break;
case FooRecoverReset:
// -- error:recovery:task:defaults
// reset the affected task
// ...
break;
default:
// -- RESERVED
break;
}
case NSUserCancelledError:
// identify the chosen recovery task
switch (anIdx)
{
case FooRecoverRedo:
// -- error:recovery:task:redo
// repeat the affected task
// ...
break;
case FooRecoverReset:
// -- error:recovery:task:reset
// reset the affected task
// ...
break;
default:
// -- RESERVED
break;
}
default:
// -- unsupported error
return (NO);
}
// return the recovery result
return (YES);
}
@end
Conclusion
Writing error-tolerant code helps improve the overall user experience of software applications. It can keep user data intact and secure, and it can ensure a stable and reliable product.
The Cocoa framework provides two classes for writing error-tolerant code for an OS X or iOS application. With the NSError class, we can encapsulate the error and any data describing the error. With the NSResponder class, we can dispatch the error object and respond to it from key points in the application.
In this article, we discovered how to create an NSError object and how to supply relevant data through a support dictionary. We learned how a Cocoa application prepares its response chain, and how the error object travels in that chain. We learned how to dispatch an error object, as well as how to respond to it. Finally, we reviewed how to prepare and attach a recovery object for the error.
Further Reading
Error Handling Programming Guide [PDF]. iOS Developer Library.
NSError Class Reference [PDF]. iOS Developer Library.
NSErrorRecoveryAttempting Protocol Reference [PDF]. iOS Developer Library.
Marcus Zara. Using NSError To Great Effect. Cocoa is my Girlfriend. Zarra Studio.
José Cruz is a freelance engineering writer based in British Columbia. He frequently contributes articles to MacTech, REALStudio Developer, and Dr. Dobb's.


