4

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

Houman
  • 64,245
  • 87
  • 278
  • 460
  • This http://stackoverflow.com/a/833124/790842 answers your question. – iphonic Dec 18 '14 at 10:02
  • 1
    There is no reason why the `NSError` will already be populated unless this is not the first time `newsFromJSON` has been called or something else is called beforehand. In the code you show it should be `nil`. – Droppy Dec 18 '14 at 10:04
  • @Droppy thanks for leading the discussion. :) I have updated the question with more details. But it remains a mystery to me. – Houman Dec 18 '14 at 11:43
  • OK it looks to me like the debugger is showing you the stack (it's possible it *thinks* the `objectNotation` parameter is the `domain` member of the `NSError`). Try using the debugger console with commands like `p *error`. – Droppy Dec 18 '14 at 11:49
  • Do you know the difference between a "thing" and a pointer to said "thing"? – Hot Licks Dec 18 '14 at 18:23
  • @HotLicks who are you addressing? – Droppy Dec 18 '14 at 19:28
  • @HotLicks Yes I understand that, but it doesn't help solving the problem here. The question should be pretty clear here, not sure why you downvoted the question. – Houman Dec 18 '14 at 20:26

3 Answers3

3

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.

enter image description here

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.

Alex Andrews
  • 1,498
  • 2
  • 19
  • 33
  • I don't think the OP is asking about what `**` means (despite the title). He wants to know how the `NSError` is already populated. – Droppy Dec 18 '14 at 10:03
  • `NSError` gets populated on the called side, __because__ you pass the address of the pointer (double indirection). – Nicolas Miari Dec 18 '14 at 10:11
  • @NicolasMiari So explain why the `NSError` object appears to be populated. – Droppy Dec 18 '14 at 10:14
  • @Droppy Read my answer, and that of @uchuugaka. It is populated because you are passing the address of the variable `error` (i.e., `&error`), so that the called method can "populate" it (by allocating a new `NSError` instance and assigning it to the dereferenced pointer-to-pointer `*(&error)`. This is C. – Nicolas Miari Dec 18 '14 at 10:17
  • @NicolasMiari That doesn't answer my question. The `NSError` object itself appears to be populated (I understand that the pointer to the `NSError` object is populated), but why does the `NSError` domain show `@"Fake Json"`? – Droppy Dec 18 '14 at 10:18
  • You mean the properties of the `NSError` object being initialized? Well, I guess that is because the side that creates the `NSError` instance (the method that accepts the `NSError**` parameter) also conveniently fills it with the appropriate information (e.g. error message). That is the whole point: this `NSError` instance is created by the method in order to report some error that occurred during the execution of the method. If the method succeeds, the instance is not created and `error` stays as `nil`. This is a very common pattern in Cocoa (again, see @uchuugaka's answer). – Nicolas Miari Dec 18 '14 at 10:21
  • But that's not what the OPs code shows. He passes a pointer to a `nil` `NSError` object and that is what's confusing him I believe. I think he also understands how pointer-to-pointer works and is used to pass objects back to the caller but doesn't understand why it appears to be populated given his code doesn't do that (or at least the portions he has shown). – Droppy Dec 18 '14 at 10:23
  • Well, my answer and the other address precisely that: that the `NSError` instance is created and (conveniently) populated inside the called method. It is a common pattern seen in Cocoa. – Nicolas Miari Dec 18 '14 at 10:26
  • Nope; both answers explain how and why pointer-to-pointer is used but not why the `NSError` object is populated (or appears to be). It's even possible it's an `lldb` bug/feature. – Droppy Dec 18 '14 at 10:27
  • I see what you mean: the called method is his own code (not some Apple framework), and it does **not** allocate the `NSError`. Is that what you mean? If that is what he means, and that is happening, then it must be a bug. Possibly ARC doing some unsolicited stuff? – Nicolas Miari Dec 18 '14 at 10:29
  • I have fixed my answer. Sorry for the long discussion. – Nicolas Miari Dec 18 '14 at 10:33
0

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

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
0

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

uchuugaka
  • 12,679
  • 6
  • 37
  • 55
  • 2
    Why does the `NSObject` appear to be populated (see the `domain` value of `@"Fake Json"`)? – Droppy Dec 18 '14 at 10:28
  • You should never care or check NSError if your return value was non nil or non NO. (Depending on the documented method called) NSError is not going to give a valuable meaning unless your method return indicates you should check the error – uchuugaka Dec 18 '14 at 17:28
  • It's a Cocoa convention that NSError should be considered meaningless if you get an expected return. Under the hood the frameworks probably keep error values in a familiar place in memory for optimizations and or framework debugging. – uchuugaka Dec 18 '14 at 17:31
  • Sounds like you don't know why. – Droppy Dec 18 '14 at 19:26
  • Haha. As I told you. As it is. Why would it have something in it? Don't know. Don't need to. I know that you shouldn't care what's in it unless the API doc says the conditions are right to care what's in it. – uchuugaka Dec 18 '14 at 22:02
  • Here you are. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html – uchuugaka Dec 19 '14 at 04:25
  • I am familiar with the concept of passing back objects via pointer-to-pointer, as is the OP, however the OP (and me) would like the know why the un-initialised error object appears to be populated. You don't appear to appreciate that and continue with your lectures about pointer-to-pointer and cocoa concepts and that is not what the issue is about. – Droppy Dec 19 '14 at 06:11
  • The point is that you are not supposed to care what's in there unless you get back a nil or NO. It doesn't matter what or why. That's the only API contract. – uchuugaka Dec 19 '14 at 07:31
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/67302/discussion-between-uchuugaka-and-droppy). – uchuugaka Dec 19 '14 at 07:32