0

The general use pattern for passing NSError is using pointer to pointer like this:

- (id)doSomethingWithObj:(NSObject *)obj error:(NSError *__autoreleasing *)error
{

    NSLog(@"error parameter: %p", error);
    id object = obj;
    if (object != nil)
    {
        return object;
    }
    else
    {
        NSError *tmp = [NSError errorWithDomain:@"after" code:0 userInfo:@{@"after": @"boris"}];
        *error = tmp;
        return nil;
    }
}

I know that since passing the address of the outside error pointer would allocate or replace the origin error, the above pattern would work.

But my issue about NSError or, more generic, Objective-C objects, is that, why I would get error if I wrote like this:

enter image description here

Instead of using NSError **, I tried NSError *, and what I was thinking is that, since now the parameter error is a type of NSError *, so *error should be the content that the pointer (error) was pointing to, then *error = [NSError errorWith...]; could replace the origin outside *error content.

But it didn't work like what I was thinking but popped up such above error.

So what confused me is that why *error = [NSError errorWith...]; does not work for the type of NSError * but work for NSError **.

bolizhou
  • 1,208
  • 1
  • 13
  • 31
  • 1
    Quick note; you need `if (error) *error = tmp;`. I.e. you must check to see if `error` is non-`nil` before assigning to it. – bbum Aug 19 '16 at 20:41

1 Answers1

1

When you pass a parameter into a method it is copied and later inside your method you deal with that copy:

- (void)changeA:(NSInteger)aToChange {
    aToChange = 7;
}

Then, when you use that method you won't be able to change the value you passed from the inside of that method:

NSInteger a = 42;
[smb changeA:a];
// a is still 42

That's because inside -changeA: you deal with a copy of arguments, i.e. so you do something like (pseudo code):

// inside changeA:
NSInteger aToChange = a; // (value is copied)
aToChange = 7; // this does not modify a, right?

You might even get the address of that aToChange and try to do something with it:

// inside changeA:
NSInteger aToChange = a; // (value is copied)
NSInteger *pointerToCOPY = &aToChange; // get address of aToChange
NSInteger *pointerToA = &a; // get address of a
// here pointerToCOPY != pointerToA

So, going back to your code:

NSError *error = nil; // outer error, passed as a parameter
...
- (id)doSomethingWithObj:(NSObject *)obj error:(NSError *)error
{
    // NSError *error(local copy) = error(passed argument);
    error = tmp; // here error is the pointer to the local error copy
    return nil;
}

As it was before with aToChange, you can't modify your external NSError error * from inside your method. That's why double pointers are used, you send pointer to your pointer, so that you still have access to the external error reference and can set it.

- (id)doSomethingWithObj:(NSObject *)obj error:(NSError **)error
{
    // NSError **error(local copy) = error(passed argument);
    *error = tmp; // Here *error is the pointer to the local error copy,
    // When you dereference error you get NSError *, which you can set,
    // that's your external error.
    return nil;
}

Note than it's impossible to do this:

NSError *error = nil;
NSError *otherError = [NSError error with];
*error = otherError;

error is a pointer to a struct, *error is a struct, the one with single isa field (from the point of the id type defenition). It's inappropriate to assign pointer to error (otherError, i.e. platform pointer size int) to a struct.

And then, the last question remains, why can't you:

NSError *error = nil;
NSError *otherError = [NSError error with];
*error = *otherError; // why can't I assign to structs?

I'm not completely sure, but, maybe memory management and ABI comes in the game, here it could just copy bunch of bytes from one object to another as they are just a C structs.

As you know, Objective-C is a superset of C, and it is possible to assign structs there: Assign one struct to another in C. That's Objective-C peculiarity dealing with objects, I think. Everything is pretty much dynamic, you can create classes at the runtime and add instance variables (Mike Ash on that topic), so it is not known at the compile time what is beyond the isa field in the struct and, actually id is just a pointer to that struct with a single isa. That's not a good idea to just copy that single isa struct and say you're done, things will fall apart.

Community
  • 1
  • 1
MANIAK_dobrii
  • 6,014
  • 3
  • 28
  • 55
  • @Avi yes, but that should be detected even earlier at compile time, compiler must not allow this, as it is impossible to cast pointer to error to a struct (objective c Object), they are of incompatible type. – MANIAK_dobrii Aug 19 '16 at 14:27