How can I use NSError in my iPhone App?
Well, what I usually do is have my methods that could error-out at runtime take a reference to a NSError
pointer. If something does indeed go wrong in that method, I can populate the NSError
reference with error data and return nil from the method.
Example:
- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
// begin feeding the world's children...
// it's all going well until....
if (ohNoImOutOfMonies) {
// sad, we can't solve world hunger, but we can let people know what went wrong!
// init dictionary to be used to populate error object
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
// populate the error object with the details
*error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
}
// wohoo! We fed the world's children. The world is now in lots of debt. But who cares?
return YES;
}
We can then use the method like this. Don't even bother to inspect the error object unless the method returns nil:
// initialize NSError object
NSError* error = nil;
// try to feed the world
id yayOrNay = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!yayOrNay) {
// inspect error
NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
We were able to access the error's localizedDescription
because we set a value for NSLocalizedDescriptionKey
.
The best place for more information is Apple's documentation. It really is good.
There is also a nice, simple tutorial on Cocoa Is My Girlfriend.
Objective-C
NSError *err = [NSError errorWithDomain:@"some_domain"
code:100
userInfo:@{
NSLocalizedDescriptionKey:@"Something went wrong"
}];
Swift 3
let error = NSError(domain: "some_domain",
code: 100,
userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])
I would like to add some more suggestions based on my most recent implementation. I've looked at some code from Apple and I think my code behaves in much the same way.
The posts above already explain how to create NSError objects and return them, so I won't bother with that part. I'll just try to suggest a good way to integrate errors (codes, messages) in your own app.
I recommend creating 1 header that will be an overview of all the errors of your domain (i.e. app, library, etc..). My current header looks like this:
FSError.h
FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;
enum {
FSUserNotLoggedInError = 1000,
FSUserLogoutFailedError,
FSProfileParsingFailedError,
FSProfileBadLoginError,
FSFNIDParsingFailedError,
};
FSError.m
#import "FSError.h"
NSString *const FSMyAppErrorDomain = @"com.felis.myapp";
Now when using the above values for errors, Apple will create some basic standard error message for your app. An error could be created like the following:
+ (FSProfileInfo *)profileInfoWithData:(NSData *)data error:(NSError **)error
{
FSProfileInfo *profileInfo = [[FSProfileInfo alloc] init];
if (profileInfo)
{
/* ... lots of parsing code here ... */
if (profileInfo.username == nil)
{
*error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:nil];
return nil;
}
}
return profileInfo;
}
The standard Apple-generated error message (error.localizedDescription
) for the above code will look like the following:
Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"
The above is already quite helpful for a developer, since the message displays the domain where the error occured and the corresponding error code. End users will have no clue what error code 1002
means though, so now we need to implement some nice messages for each code.
For the error messages we have to keep localisation in mind (even if we don't implement localized messages right away). I've used the following approach in my current project:
1) create a strings
file that will contain the errors. Strings files are easily localizable. The file could look like the following:
FSError.strings
"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."
2) Add macros to convert integer codes to localized error messages. I've used 2 macros in my Constants+Macros.h file. I always include this file in the prefix header (MyApp-Prefix.pch
) for convenience.
Constants+Macros.h
// error handling ...
#define FS_ERROR_KEY(code) [NSString stringWithFormat:@"%d", code]
#define FS_ERROR_LOCALIZED_DESCRIPTION(code) NSLocalizedStringFromTable(FS_ERROR_KEY(code), @"FSError", nil)
3) Now it's easy to show a user friendly error message based on an error code. An example:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code)
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
Great answer Alex. One potential issue is the NULL dereference. Apple's reference on Creating and Returning NSError objects
...
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
if (error != NULL) {
// populate the error object with the details
*error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
...