Reputation: 66370
I am following the book Test-Driven iOS development by G. Lee and came across this unit test, which I don't understand. First of all, if you need more code, please let me know right away.
-(void)testDelegateNotifiedOfErrorWhenNewsBuilderFails
{
MockNewsBuilder *builder = [MockNewsBuilder new];
builder.arrayToReturn = nil;
builder.errorToSet = underlyingError;
newsManager.newsBuilder = builder;
[newsManager receivedNewsJSON:@"Fake Json"];
...
}
-(void)receivedNewsJSON:(NSString *)objectNotation
{
NSError *error = nil;
// As you see error is nil and I am passing in a nil error.
NSArray *news = [_newsBuilder newsFromJSON:objectNotation error:&error];
...
}
@implementation MockNewsBuilder
-(NSArray *)newsFromJSON:(NSString *)objectNotation error:(NSError **)error
{
// But once I arrive here, error is no longer nil.
// (NSError **) error = 0x00007fff5cb887f0 domain: @"Fake Json" - code: 0
...
}
How is error auto-magically set?
UPDATE:
Thanks everyone for active discussion and advice. The answers explain how the caller side gets the error instance because of &, I understand that clearly. My question remains though why the callee side is pointing to a populated NSError instance, even though it had to be nil. I didn't set the error instance within newsFromJSON:error:
so how is it already populated there?
I just changed [newsManager receivedNewsJSON:@"Fake Json1"];
and the error instance within newsFromJSON:error:
reflects right away
(NSError **) error = 0x00007fff5b9b27f0 domain: @"Fake Json1" - code: 0
. Its very confusing...
Upvotes: 4
Views: 472
Reputation: 1498
This is just pointer to pointer concept. You are passing the reference to the reference error object &error to the method -(NSArray *)newsFromJSON:(NSString *)objectNotation error:(NSError **)error;
And this will update the error object at the memory pointer you have passed.
See this is the concept of pointer to pointer.
Update:
Your error object is nil, yes its right. But you are not passing that error object to the newsFromJSON
method, but the memory address of the error object( &error
). That is the memory address of the error object.
This why you are getting non null value there inside your newsFromJSON
method.
And one more thing, you can access the original object in your newsFromJSON
method using the content of operator(*
operator)
like **error = something;
This will update your original object ( NSError *error
) you declared in your caller method.
In C or CPP or Objective-C, & is the Address of operator and * is the content of operator.
&obj -> give the memory address of the obj
*obj -> give the content of the memory address in the obj.
Upvotes: 3
Reputation: 16256
error
is a variable of type NSError*
, that is "pointer to NSError" (in objective-C, all objects are handled as references, as opposed to e.g. C++).
What this means is that error
is a (local) variable that stores the address of the actual NSError
object, initially nil
.
The method you call creates an (autoreleased) NSError
instance. In order to get a reference to that instance back, you need to pass the method the address of the pointer, or &error
, which is, in turn, of type "pointer to pointer to NSError
" (note the two-level indirection).
You do this because arguments to functions in C and methods in Objective-C are passed by value: if you just passed error
, the value stored there (nil
) alone is copied, and no matter what the called method does, the contents of the variable error
on your side (the caller) can't be modified. To achieve this, you need to pass the address of error, or &error
.
This way, the called method can "change" the contents of error
(the address held there) so that it points to the newly created, NSError
instance.
Does it make sense?
ADDENDUM: This is a very common pattern very often seen in Cocoa: The method being called could potentially fail, and instead of just using the return value to signal success/failure, and additional 'in/out' parameter is passed to retrieve detailed error information in case of failure. On failure, the method can return false
(NO
, 0
, etc.), but in addition in can provide a more detailed error report (e.g. the reason for failure) inside the NSError
instance.
EDITED: As @Droppy said, and seeing that all code involved is your own (i.e., not some first or third party framework), it is impossible that error
is set to anything other than nil
unless you explicitly allocate it somewhere. Perhaps you should "watch" it in the debugger to see when/where it is set. since the message seems to be set to @"Fake JSON"
, the first thing you could do is search that string in your project (all files).
Upvotes: 0
Reputation: 12782
** is a pointer to a pointer. It means you need to pass a pointer address to a function or method. Objective-C is a strict superset of C. That means as in C functions and methods can only return one value. There are two ways about it. One is to wrap all your returns in structs or NSDictionaries or other collections. This way is called an outParameter It's passing a pointer address in. C is a by copy language. But pointers are portable black holes that allow you to do wild things in C. Objective-C and C++ give you the same wildness.
The error is set by Apple's framework code. The Cocoa pattern is usually to return a BOOL and pass in an NSError pointer address. If BOOL is NO check the NSError. Apple framework will have put some presents in your NSError pointer address box.
Sometimes they don't use BOOL and instead return an object or nil.
Core Foundation C frameworks work very similarly and use in and out parameters a lot.
Upvotes: 0