Pergunta

How do I define a method that returns an error as well as a value?

For instance, when I call the managedObjectContext save method, the method returns a boolean, along with an error:

if(![context save:&error]) {
    NSLog(@"%@", error);
}

Would you be able to give me a straight-forward example of the method definition behind this?

Edit/Update: In the same regard, how would it be possible to pass back multiple errors. I'm doing something wrong below (I probably don't understand the concept yet), which doesn't work:

NSArray *errors = nil;
[self throwMultipleErrors:&errors];
for(id error in errors) {
    NSLog(@"Muliple error: %@", error);
}...

-(BOOL)throwMultipleErrors:(NSMutableArray **) errors {
    [*errors addObject:@"First Error"];
    [*errors addObject:@"Second Error"];
    [*errors addObject:@"Third Error"];

    return YES;
}
Foi útil?

Solução

In your example, the method signature is:

- (BOOL)save:(NSError **)error;

This allows the caller to pass in an NSError as an address rather than a pointer (pointer to a pointer, ** and &), that the method can then assign from another scope. And if the method returns false, the caller can then check the contents of the error variable, as shown in your example.

See this question for more information on using & in front of variables and (NSError **) signatures.

Why is `&` (ampersand) put in front of some method parameters?

Example implementation:

    - (BOOL)save:(NSError **)error
    {
       NSAssert(error != nil, @"Passed NSError must be nil!");
       // Attempt to do a save
       if (saveDidFail) {
             NSDictionary *userInfo = [NSDictionary dictionaryWithObjectAndKeys:@"Save Failed", kCustomErrorKey, nil];
             *error = [NSError errorWithDomain:@"domain" code:999 userInfo:userInfo];
             return NO;
       } 
       return YES;
    }

Outras dicas

Using your example, the method signature would be:

-(BOOL)save:(NSError**)error;

Now, when using double pointers, you need to be defensive in your programming. First, you must make sure that the value of error is not nil, then *error is nil. You must do this because you don't know what to do memory management wise with an existing object. For example:

NSError *error = nil;
[self save:&error];

would be correct. But

NSError *error = [[NSError alloc] init];
[self save:&error];

is a problem because your save: method won't if error is retained or autoreleased. If you release it and it's autoreleased then your app will eventually crash. Conversely, if you don't release it and it is retained you leak memory. I recommend checking for this with an assert so that the problem is taken care of quickly before code is checked in.

NSAssert(!error || !*error, @"*error must be nil!");

Finally, when setting *error you must make sure that error is not nil. You are never permitted to set the value of memory address 0x0. If you don't check and if you pass nil into the method, you will crash if you try to set it.

if (error)
{
    *error = [NSError ...];
    return NO;
}

To return an array I would do something like this:

-(BOOL)throwMultipleErrors:(NSError **) error {

    NSAssert(!error | !*error, @"*error must be nil");

    NSMutableArray *errorList = nil;

    if (error)
        errorList = [NSMutableArray array];


    [errorList addObject:@"First Error"];
    [errorList addObject:@"Second Error"];
    [errorList addObject:@"Third Error"];

    if (error && [errorList count] > 0)
    {
        *error = [NSError errorWithDomain:@"Some error"
                                     code:0
                                 userInfo:@{@"suberrors" : [NSArray arrayWithArray:errorList]}];
        return NO;
    }

    return YES;
}

Note that the return array is immutable and is instantiated inside the method. There's really no reason for this array to be mutable outside of this method and for the array to be instantiated outside of it. Encapsulating the array in an NSError permits it to easily fit into an existing chain of NSError methods.

Notes

Of course, you would actually throw your [self save:&error]; call into an if() statement to check the return value. Do note, however, that we pass in &error rather than just error. This is because we need to pass in the pointer to error, not error itself. &error reads as "The address of error."

We need to be careful with syntax here. When we pass an NSError into a method: + (instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError * *)error

we hand in a handle (pointer to a pointer) to an error object which has not yet been initialized. Should the called message run into trouble it will initialize and fill the error object. It will NOT "return" an error, simply fill in values to a passed parameter.

so if I want to define a message that returns a value, and also takes an error parameter,Follow this tutorial: http://www.cimgf.com/2008/04/04/cocoa-tutorial-using-nserror-to-great-effect/

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top