In Objective-C, I'm trying to encapsulate multiple error-able calls and “return” the most useful error

StackOverflow https://stackoverflow.com/questions/2908300

Question

I put "return" in quotes because I don't want to literally return it. I want to do it similar to how you pass a pointer-to-a-pointer for [NSString stringWithContentsOfFile:usedEncoding:error:].

I would like to make parseFiles:error return nil and have the error reference that was passed in contain the first or second error, depending on which one failed. It seems like a Cocoa way to do it?

EDIT: Sorry, I should've been more clear about where I was having the problem. If the first path is bogus, it functions as I want. (I get the error instance outside and it prints.) If the first path is legit, as it filler string below implies, I get EXC_BAD_ACCESS.

But now I fixed it. I need to refer to it as *error inside the parseFiles:error: method and use == nil when checking if it failed. I thought I could just to if (error)...

EDIT 2 Ok, it doesn't work. I'm getting EXC_BAD_ACCESS. I'm not sure what I'm doing wrong with the conditions that check for the errors.

@implementation PassingError

- (id)init {
    self = [super init];

    NSError *error;
    [self parseFiles:@"/untitled.py" error:&error];

    if (error != nil) {
        NSLog(@"I failed because: %@", error);
    }
    return self;
}

// Wraps with reading errors.
- (NSString *)parseFiles:(NSString *)path error:(NSError **)error {

    NSStringEncoding enc1;
    NSString *contents1 = [NSString stringWithContentsOfFile:path
                                               usedEncoding:&enc1 error:*&error];

    // there was a read error

    // I need an asterisk here...
    if (*error != nil) {
        // ...and also one here
        NSLog(@"FIRST ERROR: %@", *error);
        return nil;
    }


    // here is where you'd do something that might cause another error,
    // I'll just try and read a second file for simplicity
    NSStringEncoding enc2;
    NSString *contents2 = [NSString stringWithContentsOfFile:@"/untitled.py"
                                               usedEncoding:&enc2 error:*&error];

    // there was a SECOND error
    if (*error != nil) {
        NSLog(@"SECOND ERROR: %@", *error);
        return nil;
    }


    // return both or whatever
    return [NSArray arrayWithObjects:contents1, contents2, nil];
}

@end
Was it helpful?

Solution

Passing pointers around in Objective-C can get confusing. I remember having trouble grasping what needed to be done. When you have a method like this:

- (BOOL) saveValuesAndReturnError:(NSError **) error
{
    BOOL success = [self doSomethingImportant];

    if (!success && error)
    {
        // Unsuccessful and error is a valid ptr-to-ptr-to-NSError.

        // Basically, someone has given us the address of a (NSError *).
        // We can modify what that pointer points to here.
        *error = [NSError errorWithDomain:@"myDomain" code:100 userInfo:nil];
    }

    return success;
}

This is intended to be invoked like this:

// If the caller doesn't care that it failed:
[someObject saveValuesAndReturnError:NULL];



// Or, if the caller wants to get error information on failure
NSError *anError = nil;
BOOL success;

// pass address of our (NSError *)
success = [someObject saveValuesAndReturnError:&anError];
if (!success)
{
    // anError now points to an NSError object, despite being initialised to nil,
    // because we passed the address of our NSError ptr, the method was able to 
    // change where `anError` points to.
    NSLog (@"An error occurred while saving values: %@", anError);
}

Perhaps a very relevant read in this case is a CIMGF blog post covering exactly this topic.

However...

I remember reading a while ago that methods that return errors via method arguments such as stringWithContentsOfFile:usedEncoding:error: make no guarantee not to modify the error argument for success. In other words, you cannot rely on the value of the error parameter if the method succeeded. In your particular case, it may be better to do:


- (NSString *)parseFiles:(NSString *)path error:(NSError **)error {

    NSStringEncoding enc1, enc2;
    NSError *innerError;
    NSString *contents1 = [NSString stringWithContentsOfFile:path
                                                usedEncoding:&enc1
                                                       error:&innerError];

    if (contents1 == nil)
    {
        if (error) *error = innerError;
        return nil;
    }

    NSString *contents2 = [NSString stringWithContentsOfFile:@"/untitled.py"
                                                usedEncoding:&enc2
                                                       error:&innerError];

    if (contents2 == nil)
    {
        if (error) *error = innerError;
        return nil;
    }

    // do whatever with contents1 and contents2
}

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top