8

why is "error:&error" used here (objective-c)

NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];

wouldn't an object in objective-c be effectively pass-by-reference anyway?

Greg
  • 31,898
  • 75
  • 232
  • 424

2 Answers2

22

The argument type for error: is NSError** (i.e. a pointer to a pointer to an object). This permits the moc object to allocate and initialize a new NSError object as required. It is a common pattern, especially in Cocoa.

The NSError documentation gives some indication of the motivation for this approach:

Applications may choose to create subclasses of NSError to provide better localized error strings by overriding localizedDescription.

Passing in an NSError** argument allows that method to return any subclass of NSError that makes sense. If you passed in NSError*, you would have to supply an existing NSError object, and there would be no way for the method to return a different object from the one you passed in.

To be clear, the method could look something like this:

- (NSArray*)executeFetchRequest:(Request *)request error:(NSError**)error {
    ...
    if ((error != NULL) && (some_error_condition)) {
        *error = [[[SomeNSErrorSubclass alloc] init...] autorelease];
        return nil;
    }
}

Note that this also allows the calling code to ignore errors by simply passing in NULL for the error: parameter, as follows:

NSArray *array = [moc executeFetchRequest:request error:NULL];

Update: (in response to questions):

There are two reasons why the argument type has to be NSError** instead of NSError*: 1. variable scoping rules, and 2. NSError instances are imutable.

Reason #1: variable scoping rules

Let's assume that the function declaration were to look like this:

- (NSArray*)executeFetchRequest:(Request *)request error:(NSError*)error;

And we were to call the function like this:

NSError * error = nil;
[someArray executeFetchRequest:someRequest error:error];
if (error != nil) { /* handle error */ }

When you pass in a variable this way, the function body will not be able to modify the value of that variable (i.e. the function body will not be able to create a new variable to replace the existing one). For example, the following variable assignments will exist only in the local scope of the function. The calling code will still see error == nil.

- (NSArray*)executeFetchRequest:(Request *)request error:(NSError*)error {
    ...
    error = [[[NSError alloc] init...] autorelease];             // local only
    error = [[[SomeNSErrorSubclass alloc] init...] autorelease]; // local only
}

Reason #2: instances of NSError are immutable

Let's keep the same function declaration, but call the function like this:

NSError * error = [[[NSError alloc] init...] autorelease];
[someArray executeFetchRequest:someRequest error:error];
if (error != nil) { /* handle error */ }

First of all, the variable scoping rules guarantee that error can not be nil, so the if (error != nil) { ... condition will always be true, but even if you wanted to check for specific error information inside the if block, you would be out of luck because instances of NSError are immutable. This means that once they are created, you cannot modify their properties, so the function would not be able to change the domain or userInfo of that NSError instance that you created in the calling code.

- (NSArray*)executeFetchRequest:(Request *)request error:(NSError*)error {
    ...
    error.domain = ...   // not allowed!
    error.userInfo = ... // not allowed!
}
e.James
  • 109,080
  • 38
  • 170
  • 208
  • 1
    @Kevin Ballard: That's true. It should be an `autoreleased` object. I have changed it. – e.James Nov 30 '11 at 23:56
  • Im almost getting this. Just one question...why this = "and there would be no way for the method to return a different object from the one you passed in". IOW, why do we need a different object than the one passed in? Thx in advance – marciokoko Aug 17 '13 at 15:30
  • @marciokoko: The answer was too long for a comment, so I added it above. Hope that helps! – e.James Aug 18 '13 at 17:18
  • WOW! I actually think I understand it :-) You need to create an error instance to fill it when the method returns. Since you don't know what error it will be, you must be able to modify it somehow. But NSError instances are immutable & furthermore cannot be changed in-scope, so we pass a pointer to a pointer. Last Q, couldnt we just create the NSError instance inside the method? – marciokoko Aug 19 '13 at 15:52
  • @marciokoko: Glad to hear it! `:)`. The NSError instance should definitely be created inside the method, but it will only exist in local scope unless you use the NSError** argument. You can try that one out for yourself with a simple function in a test program if that helps? – e.James Aug 19 '13 at 17:16
  • Could you join this room http://chat.stackoverflow.com/rooms/35802/ios-pointer-to-a-pointer – marciokoko Aug 19 '13 at 21:14
  • @e.James "if ((error != NULL) && (some_error_condition)) " should read "if ((*error != NULL) && (some_error_condition)) " – Elise van Looij Dec 15 '14 at 10:09
3

It's effectively another return value. The error is not dominant by convention in Cocoa when there is a return value for the operation. When an error is encountered, it may be returned to you by this out parameter.

In the case of NSError, it works this way because NSError is not a mutable type - its fields are set at initialization and never mutated. Therefore, you cannot pass an NSError as usual and set the error code.

justin
  • 101,751
  • 13
  • 172
  • 222
  • 2
    +1 for describing `error:(NSError**)error` as an out parameter, and for explaining that `NSError` objects cannot be mutated after initialization. I missed both of these in my answer. – e.James Nov 30 '11 at 23:54