Coocoo4Cocoa
Coocoo4Cocoa

Reputation: 50876

Why does NSError need double indirection? (pointer to a pointer)

This concept seems to trouble me. Why does an NSError object need its pointer passed to a method that is modifying the object? For instance, wouldn't just passing a reference to the error do the same thing?

NSError *anError;
[myObjc doStuff:withAnotherObj error:error];

and then in doStuff:

 - (void)doStuff:(id)withAnotherObjc error:(NSError *)error 
 {
    // something went bad!
    [error doSomethingToTheObject];
 }

Why doesn't the above work like most other object messaging patterns work? Why must instead we use error:(NSError **)error?

Upvotes: 47

Views: 11440

Answers (5)

Smart Home
Smart Home

Reputation: 811

I still did not get the full picture by reading all the answers above. The layman exercise I did below, finally helped me understand what is happening. Just putting it out there in case it helps other beginners.

Suppose you have following

@interface Class X
-(void) methodX:(NSMutableArray *)array;
@end

In some other part of the code you have the following sequence

ClassX *objectX = [[ClassX alloc] init];
NSMutableArray *arrayXX = [@[@(1), @(2)] mutableCopy]; 
//What is stored in arrayXX is the address in the heap at which the NSMutableArray object starts, lets call this address ZZZ
//array starting at address ZZZ in the heap now contains NSNUmbers @1,@2
[objectX methodX:array]

When you invoke [objectX methodX:array], what is being received by the method is a copy of array. Since array contains an address (i.e is a pointer), the copy is special in that what is received is another variable with address ZZZ in it.

So, if methodX does [array removeObjectAtIndex:0], then the object starting at address ZZZ gets affected (now only contains one NSNUmber @(2)). So, when the method returns, the original array also gets affected.

Suppose instead methodX does array = [@[@(2)] mutableCopy]; then original array does not get affected. This is because you did not go into address ZZZ and change something. Instead you overwrote the ZZZ in the copy received by the method to a different address YYY. YYY address is the start of a NSMUtableArray object with one element NSNUmber @(2). The original ZZZ address still contains a NSMUtableArray with two elements. @(1) and @(2). So, when method returns, the original array is unaffected.

Upvotes: 0

n8gray
n8gray

Reputation: 4959

The NSError** pattern is used when a method normally returns some value but instead may need to return an error object (of type NSError*) if it fails. In Objective-C a method can only return one type of object, but this is a case where you want to return two. In C-like languages when you need to return an extra value you ask for a pointer to a value of that type, so to return an NSError* you need an NSError** parameter. A more realistic example would be this:

// The method should return something, because otherwise it could just return
// NSError* directly and the error argument wouldn't be necessary
- (NSArray *)doStuffWithObject:(id)obj error:(NSError **)error
{
  NSArray *result = ...;  // Do some work that might fail
  if (result != nil) {
    return result;
  } else {
    // Something went bad!
    // The caller might pass NULL for `error` if they don't care about
    // the result, so check for NULL before dereferencing it
    if (error != NULL) {
      *error = [NSError errorWithDomain:...];
    }
    return nil;  // The caller knows to check error if I return nil
  }
}

If you only had an NSError* parameter instead of an NSError** then doStuff would never be able to pass the error object back to its caller.

Upvotes: 79

OutOnAWeekend
OutOnAWeekend

Reputation: 1453

An old question, but still I think its worth putting this here -

The actual culprit is NSError. If you look at its class reference, there are no setter methods for any of its attributes, i.e. domain, code or userInfo. So there is no way, you can just alloc and initialize a NSError, pass it to the method and then populate information on the passed NSError object. (Had there been a setter method, we could have just passed a NSError * and done something like error.code = 1 in the method.)

So in case there is an error, you have to generate a new NSError object in the method and if you are doing so the only way to pass it back to the caller is by having a NSError ** argument. (For the reason explained in the above answers.)

Upvotes: 12

rein
rein

Reputation: 33455

Quite simply:

if you pass a pointer to an object to your function, the function can only modify what the pointer is pointing to.

if you pass a pointer to a pointer to an object then the function can modify the pointer to point to another object.

In the case of NSError, the function might want to create a new NSError object and pass you back a pointer to that NSError object. Thus, you need double indirection so that the pointer can be modified.

Upvotes: 96

Peter Hosey
Peter Hosey

Reputation: 96353

Alternate statement of what n8gray said:

Because you're not receiving an object to send messages to; you're creating the object and returning it. You generally need the pointer-to-an-NSError *-variable argument because you can only use the return statement on one thing at a time, and you're already using it with NO.

Upvotes: 7

Related Questions