46

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?

Coocoo4Cocoa
  • 48,756
  • 50
  • 150
  • 175

5 Answers5

96

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.

rein
  • 32,967
  • 23
  • 82
  • 106
  • 7
    This really helps me understand. I'm used to thinking of pointers in the context of languages that distinguish between "byref" and "byval" arguments. Byref args (i.e. variables referenced by pointers) can be changed by the called function and the caller can see the change, so I didn't understand @n8gray's answer: "if you only had an `NSError*`... `doStuff` would never be able to pass the error object back to its caller". Your answer "the function can modify the pointer to point to another object" makes clear WHY you would want the `**` if the caller can already see changes with just a pointer. – stifin Aug 15 '11 at 20:45
  • That make sense, but why not just modify what the pointer is pointing to? – Will Gwo Jul 25 '18 at 00:21
79

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.

n8gray
  • 4,939
  • 3
  • 36
  • 33
11

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.)

OutOnAWeekend
  • 1,383
  • 1
  • 18
  • 36
7

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.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • Hi, Peter :-) I hope you are having a good start into the new year. While brushing up on `C` about `**` I landed here on this post. Excuse me, I still don't quite get it: if we passed only one `*` (a pointer to an object), that should suffice for making changes to the object, right? Why would we still need to pass a pointer to a pointer to an object in order to make changes to an object? Thanks very much in advance. Regards. – Unheilig Jan 08 '14 at 00:03
  • 3
    @Unheilig: “if we passed only one `*` (a pointer to an object), that should suffice for making changes to the object, right?” Right. You only need the pointer to the object to send messages to that object. “Why would we still need to pass a pointer to a pointer to an object in order to make changes to an object?” Because you're not making changes to an object; you're creating a new object and returning it to the caller. You do that by assigning it at the address the caller gave you—the pointer to the variable where you will put the pointer to the object. – Peter Hosey Jan 08 '14 at 03:41
  • Hi, @PeterHosey, I hope I won't bother you, but in your last sentence, "You do that by assigning it at the address the caller gave you---the pointer to the variable where you will put the pointer to the object." I don't quite understand it. What is the pointer you mentioned "put the pointer to the object"? Yes, caller gave me an address (`&error`), how is it working with my new `NSError` instance created inside the function (`*error = [NSError errorWithDomain:...]`)? – bolizhou Aug 19 '16 at 07:42
0

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.

Smart Home
  • 801
  • 7
  • 26