230

I am working on catching errors in my app, and I am looking into using NSError. I am slightly confused about how to use it, and how to populate it.

Could someone provide an example on how I populate then use NSError?

Bartłomiej Semańczyk
  • 52,820
  • 43
  • 206
  • 318
Nic Hubbard
  • 39,231
  • 60
  • 236
  • 403

9 Answers9

480

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.

Cœur
  • 32,421
  • 21
  • 173
  • 232
Alex
  • 58,815
  • 45
  • 146
  • 176
  • 39
    this is the funniest example, ever – ming yeow Jun 18 '11 at 10:17
  • this is a pretty awesome answer, although there are some issues in ARC and casting the `id` to a `BOOL`. Any slight ARC compatible variation would be much appreciated. – NSTJ Dec 05 '12 at 17:28
  • 8
    @TomJowett I'd be really pissed if we end up not being able to end world hunger simply because Apple pushed us to move to the newer ARC only world. – Manav Dec 19 '12 at 11:51
  • 1
    the return type can be `BOOL`. Return `NO` in case of error and instead of checking for the return value, just check for `error`. If `nil` go ahead, if `!= nil` handle it. – Gabriele Petronella Dec 30 '12 at 15:40
  • 8
    -1: You really need to incorporate code that verifies `**error` is not nil. Otherwise the program will throw an error which is completely unfriendly and doesn't make it apparent what's happening. – FreeAsInBeer Mar 07 '13 at 17:24
  • I guess Alex has production code written that will later make him laugh about his own comments... know that feeling – CularBytes Jul 19 '15 at 13:01
60

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];
Wolfgang Schreurs
  • 11,649
  • 7
  • 47
  • 89
  • 9
    Great answer! But why not put the localized description in the user info dictionary where it belongs? [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:@{NSLocalizedDescriptionKey : FS_ERROR_LOCALIZED_DESCRIPTION(error.code)}]; – Richard Venable Aug 02 '13 at 14:51
  • 1
    Is there any particular place where I should put the string file? From FS_ERROR_LOCALIZED_DESCRIPTION() I'm getting just the number (error code). – huggie Dec 10 '13 at 10:30
  • @huggie: not really sure what you mean. I usually put these macro's that I use across the whole app in a file called `Constants+Macros.h` and import this file in the prefix header (`.pch` file) so it's available everywhere. If you mean you're only using 1 of the 2 macros, that might work. Perhaps the conversion from `int` to `NSString` isn't really necessary, though I haven't tested this. – Wolfgang Schreurs Dec 10 '13 at 12:27
  • @huggie: ow, I think I understand you now. The strings should be in a localisable file (`.strings` file), since that's where Apple's macro will look. Read about using `NSLocalizedStringFromTable` here: https://developer.apple.com/library/mac/documentation/cocoa/conceptual/loadingresources/Strings/Strings.html – Wolfgang Schreurs Dec 10 '13 at 12:31
  • I asked because for some reason I'm getting the code "1001", "1002" instead of the meaningful phrases "Logout failed." – huggie Dec 11 '13 at 01:30
  • 1
    @huggie: Yeah, I used the localised string tables. The code in the macro `FS_ERROR_LOCALIZED_DESCRIPTION` checks the localisable string in a file called `FSError.strings`. You might want to check out Apple's localisation guide on `.strings` files if this is foreign to you. – Wolfgang Schreurs Dec 11 '13 at 02:54
  • In the case you cover all the parts of your application for including the error message this could be great, but if you are framework developer who should export your framework to another developer, then you must send it with the .strings file, which seems to be abnormal, inefficient. If you are under framework, you need to define the error messages somewhere like prefix header etc. – KoreanXcodeWorker Feb 21 '19 at 04:16
38

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;
...
jlmendezbonini
  • 2,189
  • 19
  • 25
32

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"])
AlBeebe
  • 7,863
  • 3
  • 47
  • 65
9

Please refer following tutorial

i hope it will helpful for you but prior you have to read documentation of NSError

This is very interesting link i found recently ErrorHandling

NANNAV
  • 4,718
  • 4
  • 26
  • 48
Tirth
  • 7,665
  • 9
  • 52
  • 86
3

I'll try summarize the great answer by Alex and the jlmendezbonini's point, adding a modification that will make everything ARC compatible (so far it's not since ARC will complain since you should return id, which means "any object", but BOOL is not an object type).

- (BOOL) 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
        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 NO;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

Now instead of checking for the return value of our method call, we check whether error is still nil. If it's not we have a problem.

// initialize NSError object
NSError* error = nil;
// try to feed the world
BOOL success = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!success) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
Gabriele Petronella
  • 102,227
  • 20
  • 204
  • 227
  • 3
    @Gabriela: Apple states that when using indirection variables to return errors, the method itself should always have some return value in case of success or failure. Apple urges developers to first check for the return value and **only** if the return value is somehow invalid check for errors. See the following page: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html – Wolfgang Schreurs Dec 31 '12 at 01:47
3

Another design pattern that I have seen involves using blocks, which is especially useful when a method is being run asynchronously.

Say we have the following error codes defined:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

You would define your method that can raise an error like so:

- (void)getContentsOfURL:(NSString *)path success:(void(^)(NSString *html))success failure:(void(^)(NSError *error))failure {
    if (path.length == 0) {
        if (failure) {
            failure([NSError errorWithDomain:@"com.example" code:MyErrorCodesEmptyString userInfo:nil]);
        }
        return;
    }

    NSString *htmlContents = @"";

    // Exercise for the reader: get the contents at that URL or raise another error.

    if (success) {
        success(htmlContents);
    }
}

And then when you call it, you don't need to worry about declaring the NSError object (code completion will do it for you), or checking the returning value. You can just supply two blocks: one that will get called when there is an exception, and one that gets called when it succeeds:

[self getContentsOfURL:@"http://google.com" success:^(NSString *html) {
    NSLog(@"Contents: %@", html);
} failure:^(NSError *error) {
    NSLog(@"Failed to get contents: %@", error);
    if (error.code == MyErrorCodesEmptyString) { // make sure to check the domain too
        NSLog(@"You must provide a non-empty string");
    }
}];
Senseful
  • 73,679
  • 56
  • 267
  • 405
0

Well it's a little bit out of question scope but in case you don't have an option for NSError you can always display the Low level error:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);
Mike.R
  • 2,026
  • 2
  • 22
  • 30
0
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

which I can use NSError.defaultError() whenever I don't have valid error object.

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
Hemang
  • 25,740
  • 17
  • 113
  • 171