When the deal goes down
I’m pretty sure that your code, as mine, never has bugs. It just develops random features. But you know, sometimes sheet happens, or at least that’s what I’ve been told.
In Cocoa there two mechanism to deal with, let’s say, random features. Exceptions, and error reporting. Apple strongly recommends not to use exception like in other platforms. In Cocoa, exceptions are meant to be use mostly in development, or in libraries. The recommended way to report when the app is not behaving is through NSError.
In many Cocoa APIs we will find an error parameter. They usually takes the address of an NSError variable (not the reference). Takes, for instance, - (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error. First you declare a NSError and then, you pass it to the API, if this particular call, returns nil, then you know something went wrong (it should returns an array. An empty array if nothing found, but an array). If that’s the case, the error will bring the details of what’s wrong.
NSError *error = nil;
NSArray *fetched = [context executeFetchRequest:fetchRequest
error:&error];
if ( fetched == nil ) {
NSLog(@"error: %@", error);
}
The return value of the error pointer is undefined if the method succeeded. Therefore, you should never use the value (nil or non-nil) of an NSError pointer to decide if an error happened.
NSError are composed with a domain and a code. The domain is there for historic reasons. It is sometimes useful to know the subsystem that has produced the error. The domain is string, most of the times with an inverse domain name (like net.volonbolon.subsystem). The error codes, on the other hand, are numbers. The different numbers indicate a different problem in a given subsystem. You can check the Foundation Kit error codes here.
But not everything is a code, NSError offers more information via a set of different APIs. The content returned by these APIs is composed with the content of the userInfo dictionary of the NSError. But since userInfo is not mandatory, there is only one API that is guaranteed to return a string: localizedDescription. localizedDescription is either the string stored with the NSLocalizedDescriptionKey in the userInfo dictionary, or an string constructed from the domain and the code. localizedRecoveryOptions returns an array the localized titles of buttons appropriate to display to the user to fix the issue. By default this method returns the object in the user info dictionary for the key NSLocalizedRecoveryOptionsErrorKey. localizedRecoverySuggestion returns a string containing the localized recovery suggestion for the error. This is the string stored in the userInfo dictionary with the key NSLocalizedRecoverySuggestionErrorKey. localizedRecoverySuggestion also returns a string, this time with a localized recovery suggestion for the error. This is stored in userInfo with NSLocalizedRecoverySuggestionErrorKey. The last API is localizedFailureReason that contains a localized explanation of the reason for the error. By default this method returns the object in the user info dictionary for the key NSLocalizedFailureReasonErrorKey.
Sometimes, for instance, if you are validating a Core Data Managed Object to insert in the context, you may need to append to and error produced by some native API some more information to let the use knows what’s wrong with the managed object. Here’s a snippet that do just that:
- (NSError *)errorFromOriginalError:(NSError *)originalError error:(NSError *)secondError {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
NSMutableArray *errors = [NSMutableArray array];
if ( [originalError code] == NSValidationMultipleErrorsError ) {
[userInfo addEntriesFromDictionary:[originalError userInfo]];
[errors addObjectsFromArray:[userInfo objectForKey:NSDetailedErrorsKey]];
} else {
[errors addObject:originalError];
}
[userInfo setObject:errors forKey:NSDetailedErrorsKey];
return [NSError errorWithDomain:NSCocoaErrorDomain
code:NSValidationMultipleErrorsError
userInfo:userInfo];
}
Exceptions
In Objective-C an exception should not be used as in Java. In Objective-C an exception should be used only in development, and only to catch errors in the code, never to handle expected errors, like a nil response. Exceptions are instances of NSException. They contain a name, a reason and an optional userInfo dictionary. Exceptions can be thrown with the @thrown directive, or sending the raise message to any instance of NSException.
Uncaught exceptions terminate the application, and this is perfectly fine. You can also catch them, but just to gather more information, and terminate the program yourself. Remember, in Objective-C exceptions are not about trying to keep the app running, but to discover what’s wrong. For those with experience with Java, or Python, the blocks are pretty standard:
@try {
// Trying to avoid dragons? Perform the dangerous trick here
}
@catch {
// You found the dragons. Try to get the as much information as possible, and kill the app.
}
@finally {
// This is going to be executed, whether if the exception is thrown or not.
}
(the title is borrowed from a wonderful song from Bob Dylan)