How to convert an exception into an NSError object
Asked Answered
S

3

4

I want to convert the message of an exception into an NSError object so that I can use it within a try-catch block (I'm actually working on a native iOS module for React Native).

RCT_EXPORT_METHOD(myMethod:(NSDictionary *)dict
             resolver:(RCTPromiseResolveBlock)resolve
             rejecter:(RCTPromiseRejectBlock)reject)
{
  @try {
    // Do something which could throw something (NS Error or NS Exception)
    resolve(nil);
  } @catch (NSException *exception) {
    // HERE I WANT TO TRANSFORM THE EXCEPTION exception INTO AN ERROR error
    NSError error = ???
    reject(@"my_error", @"Could not do something important", error);
  }
}

I want to convert the exception into an NSError because the third parameter of the reject function (which rejects a Promise on the JS side) expects the input to be of type NSError. I'm not sure whether my solution (using try-catch) is the best thing in this scenario..

In this Apple Developer Guide it says

You can convert an exception into an NSError object and then present the information in the error object to the user in an alert panel.

However the guide does not show a code sample for that and only shows a code sample for a second approach You can also return them indirectly in methods that include an error parameter which seems to complicated for what I want.

So, how would I convert an exception into an NSError? The API reference of NSError does not seem to contain a suitable function..

Seiter answered 22/4, 2017 at 16:16 Comment(3)
How do you wish to make use of the NSError? Do you really need an NSError?Tourcoing
@Tourcoing I edited my code snippet to better reflect my use case and explained why I want to use NSError and can't use NSException. Or could I?Seiter
Does this answer your question? How can I use NSError in my iPhone App?Lend
V
8

You can't convert NSException to NSError because NSException they do not have the same properties. Is there any reason for catching NSException instead of building a **NSError mechanism? Exceptions are usually fatal and non-recoverable while Errors are non-fatal and recoverable.

If you need to go through with converting an NSException to an NSError anyway, you can do it manually like so:

@try {
    // Something
} @catch (NSException *exception) {
    NSMutableDictionary * info = [NSMutableDictionary dictionary];
    [info setValue:exception.name forKey:@"ExceptionName"];
    [info setValue:exception.reason forKey:@"ExceptionReason"];
    [info setValue:exception.callStackReturnAddresses forKey:@"ExceptionCallStackReturnAddresses"];
    [info setValue:exception.callStackSymbols forKey:@"ExceptionCallStackSymbols"];
    [info setValue:exception.userInfo forKey:@"ExceptionUserInfo"];

    NSError *error = [[NSError alloc] initWithDomain:yourdomain code:errorcode userInfo:info];
    //use error
}
Vie answered 22/4, 2017 at 16:24 Comment(5)
The reason for catching NSException is that is what is being thrown.Tourcoing
Sorry, coming from Swift I forgot only NSExceptions can be caught in Obj-C. I've updated my answer. It should provide you with the stuff you need to convert your exception to an error.Vie
@Vie you can catch both NSError and NSException. Either with one @catch(id e) clause or one clause for each (@try { ... } @catch(NSError err) { ... } @catch(NSException ex) { ... })Amigo
@kevin what oskar was trying to say was that if an NSException * is being thrown, you can't catch it with an NSError * clauseStutter
This can be written a little more elegantly, and also needs some validation to prevent crashes - attempting to [info setValue:xxx for key:yyy] where xxx is nil (any of the exception fields used here) will crash immediately.Kaliningrad
S
2

NSError is a very flexible class that allows a very extensible error reporting system, thus nobody forbids you to do this:

/// NSExcetion * e = ...;
[NSError errorWithDomain:e.name code:0 userInfo:@{
    NSUnderlyingErrorKey: e,
    NSDebugDescriptionErrorKey: e.userInfo ?: @{ },
    NSLocalizedFailureReasonErrorKey: (e.reason ?: @"???") }];
}

The header files say about NSUnderlyingErrorKey:

The value of this key should be an NSError.

But should is not must and code that blindly relies that something found in a dictionary has a specific class is broken to begin with. Also that's the just the header; the official developer documentation of that key says no such thing and this documentation is authoritative.

And for those who wunder what e.userInfo ?: @{ } and e.reason ?: @"???" means, it's just a shorter way of writing:

e.reason != nil ? e.reason : @"???"

In the end, code may recognize certain errors and handle them in a specific way but all code must be written to accept any error, even unknown ones and offer some kind of default handling for that case. If it is your code, you know that the error domain may be an exception name, so you can check for that and third party code will just treat that as an unknown error.

Squirmy answered 15/11, 2019 at 10:47 Comment(3)
Should NSDebugDescriptionErrorKey: e.userInfo rather not be NSDebugDescriptionErrorKey: e.description ?Propylaeum
@LeslieGodwin -desrciption is a method of NSObject and just means "Give me a printable string that describes this object" which can be pretty much anything. Yet the userInfo of an exception can contain plenty of useful information about how, why and where this exception took place but the format is exception specific. The value of NSDebugDescriptionErrorKey is printed in various debug places as %@, which calls -description on its value and gives you a nice human readable printout of the userInfo dictionary of the exception.Squirmy
I think it a poor choice to translate exception name into error-domain - these are not compatible, and I'd say the programmer should provide concrete error domain for their app or library, or even workflow. In addition, I'd use as much data from the NSException to enrich the userInfo with whatever I know about the exception - using custom keys.Kaliningrad
K
0

Here is what I do in my code. My design decision was not to "convert" the exception into an NSError, and so lose much of the information in it, but rather to create my own NSError object representing the notion that "an exception has occurred" and EMBED data from the NSException into that NSError, like thus:

    NSError *err = nil;
    @try {
        //! ... Lot's of work done here...
    } 
    @catch (NSException *exception) {
        NSMutableDictionary *info = [exception.userInfo mutableCopy]?:[[NSMutableDictionary alloc] init];
        [info addEntriesFromDictionary: [exception dictionaryWithValuesForKeys:@[@"ExceptionName", @"ExceptionReason", @"ExceptionCallStackReturnAddresses", @"ExceptionCallStackSymbols"];
        [info addEntriesFromDictionary:@{NSLocalizedDescriptionKey: exception.name, NSLocalizedFailureReasonErrorKey:exception.reason }];
        err = [NSError errorWithDomain:@"myErrorDomain" code:-10 userInfo:info];
    } 
    @finally {
        //! --- wrap up and return --- 
    }

This will preserve most of the exception information, while having a concrete, identifiable NSError to hand to caller.

Kaliningrad answered 1/1, 2023 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.