Why can't the address of an ivar be passed to an "id __autoreleasing *" argument under ARC? - objective-c

Under ARC, an out-parameter takes the following form (by default; this is equivalent to NSError **):
- (BOOL)tryWithError:(NSError *__autoreleasing *)err;
From the Transitioning to ARC Release Notes, if we pass the address of a __strong local variable, the compiler will create a temporary variable and generate the following code:
NSError *error; // strong
BOOL ok = [myObject tryWithError:&error];
// translated to
NSError *__strong error;
NSError *__autoreleasing tmp = error;
BOOL ok = [myObject tryWithError:&tmp];
error = tmp;
But if we do it with an instance variable:
#implementation Foo {
NSError *_error; // strong
}
- (void)bar
{
[myObject tryWithError:&_error];
}
...
this gives us the error
Passing address of non-local object to __autoreleasing parameter for write-back.
Why is this invalid? Couldn't the compiler just translate such code automatically to this?
- (void)bar
{
NSError *__autoreleasing tmp = _error;
[myObject tryWithError:&tmp];
_error = tmp;
}
After all, this is what I will be writing anyway to solve the problem!
Note: adding the out keyword to the parameter type will reduce the compiler's work slightly because it doesn't have to read the current value into the temporary variable — but this doesn't take care of the error.

A pointer to an ivar can't be passed to an “id __autoreleasing *” argument under ARC because that kind of pass-by-writeback is ill-formed. The respective section in the ARC specification lists legal forms of pass-by-writeback, the only one applicable here is
&var, where var is a scalar variable of automatic storage duration
with retainable object
, so only automatic storage duration (a local variable) is allowed.
Why this is invalid: I am pretty sure the reason here is compatibility with older code:
1) You should only look at the error writeback in the failure case. In the success case, there is no guarantee at all what's inside the error pointer.
2) In general, whether the writeback value should be used or not depends on the contract of the method. That is something the compiler cannot check.
This is the version of the code that matches the type of &error (NSError * __autoreleasing *) to the type of the writeback (NSError ** which is interpreted as NSError * __autoreleasing *). If ok is YES, the error value won't be touched.
NSError * __autoreleasing error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// use error
}
However, those __autoreleasing are ugly, so instead of forcing us to use __autoreleasing all over the place, the compiler allows us to pass a __strong (but local) variable as well (default ownership):
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// use error
}
According to the docs, that gets rewritten to:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// use error
}
Not a problem at all, the error will only be used in the success case.
Now let's have a look at a __strong instance variable _error. Why doesn't the compiler allow that? Here is what the rewrite would look like:
NSError * __autoreleasing tmp = _error;
BOOL OK = [myObject performOperationWithError:&tmp];
_error = tmp;
if (!OK) {
// use error
}
The problem here is that the writeback in tmp would always be used (assigned to the instance variable _error), ignoring the contract of the method that the writeback should only be used in error cases (or in general whatever the documentation of the method says). A safe way to assign the last error to an instance variable would be
NSError * __autoreleasing tmp = _error;
BOOL OK = [myObject performOperationWithError:&tmp];
if (!OK) {
_error = tmp;
// use error
} else {
_error = nil; // Make sure that _error is nil if there was no error.
}
And that's only true for the convention of Cocoa's methods which return an error. In general there is no way for the compiler to tell what a method will do with an id *: There may be old methods out there that use different conventions. So if you really want to store the writeback in a __strong instance variable, you currently have to walk the extra mile yourself, and I don't expect this to change.

Related

Pointer issue about Objective-C objects [duplicate]

This question already has answers here:
Cannot understand NSError/NSObject pointer passing behavior
(2 answers)
Why does NSError need double indirection? (pointer to a pointer)
(5 answers)
Closed 6 years ago.
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:
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 **.
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.

NSError __autoreleasing vs _Nullable

I have always known this pattern was the way to manage an NSError** parameter (specifically the BOOL return value and NSError* __autoreleasing * parameter:
-(BOOL)doSomethingWithString:(NSString*)string error:(NSError* __autoreleasing *)error {
if(![string length]) {
*error = [NSError blah blah];
return NO;
}
return YES;
}
To use this method followed this pattern:
NSError* error = nil;
if([self doSomethingWithString:#"" error:&error]) {
//great success!!
} else {
//log error
}
Now it seems that __autoreleasing is replaced with _Nullable?
I'm looking at this method from the official Apple Documentation: https://developer.apple.com/reference/foundation/nsfilemanager/1415371-createdirectoryaturl?language=objc
So, do I simply replace all of my __autoreleasing with _Nullable and all is good in this situation? If not, what is the new pattern for managing NSError** parameters?
No, these are 2 different things.
__autoreleasing specifies variable mode for ARC, so it can properly deduct how to read and write to it.
Nullability specifies... well whether parameter can be nil or not.
I think the confusion is caused by fact that Clang automatically assumes __autoreleasing for indirect parameters (http://clang.llvm.org/docs/AutomaticReferenceCounting.html#indirect-parameters), so some people just omit it. And if I remember correctly it wasn't always the case.
Otherwise you would end up with * __autoreleasing _Nullable *.
But it's not a bad thing to be explicit!

__autoreleasing in error:(NSError *__autoreleasing *)outError

I noticed this pattern in Apple functions which return errors
error:(NSError *__autoreleasing *)outError
I understand the meaning, that it's pointer to pointer, used to carry out the result (using just * would change only the local copied variable, but not the outside one) but I'm concerned about the:
__autoreleasing
What happens if I leave it out? Do I get a leak? Why is it necessary?
You don't have to explicitly specify __autoreleasing when defining a function that
returns an object, for example
-(BOOL)doSomething:(NSError **)error;
The ARC compiler automatically inserts the __autoreleasing. This is explained in
the Clang/ARC documentation:
4.4.2 Indirect parameters
If a function or method parameter has type T*, where T is an
ownership-unqualified retainable object pointer type, then:
if T is const-qualified or Class, then it is implicitly qualified with
__unsafe_unretained;
otherwise, it is implicitly qualified with __autoreleasing.
The Xcode code completion
also knows about that and displays (NSError *__autoreleasing *)error.
When calling such a function the ARC compiler also automatically does
"the right thing", so you can just call
NSError *error;
BOOL success = [self doSomething:&error];
As explained in the "Transitioning to ARC Release Notes", the compiler inserts a temporary
__autoreleasing variable:
NSError *error;
NSError * __autoreleasing tmp = error;
BOOL success = [self doSomething:&tmp];
error = tmp;
(For the gory details you can read 4.3.4 "Passing to an out parameter by writeback" in
the Clang/ARC documentation.)

NSError * vs NSError **

I realize that this is similar to an existing post here, What's the Point of (NSError**)error?, but my question is a little different. I understand how the double pointer works, and how this is the common iOS API error pattern. My question is more around the single pointer, and why this code doesn't work:
- (BOOL)someMethodWithError:(NSError *)error
{
...
if( errorOccured )
{
NSError *e = [[[NSError alloc] initWithDomain:#"" code:1 userInfo:nil] autorelease];
error = e;
return NO;
}
return YES;
}
implemented using:
NSError *error = nil;
if( ![someObj someMethodWithError:error] )
{
NSLog(#"Error: %#", [error localizedDescription]);
}
Why doesn't the assignment in the method implementation reassign the pointer to the new NSError object?
I find that it helps to think of a pointer as just an integer. (That's what it is.)
Look at your example with an int.
-(BOOL)someMethodWithError:(int)error
{
error =100;
return NO;
}
That integer is pass by value. after that function is called error will not change.
error = 123;
[self someMethodWithError:error];
//error is = 123;
The pointer is the same thing. It's pass by value.
NSError * error; //this assigns this to a value in memory.
NSLog(#"%p",error);
[self someMethodWithError:error];
NSLog(#"%p",error); // the value of error will not have changed.
if you want the pointer to change you need to send in a pointer to that pointer and change it. It is confusing but draw yourself a diagram of memory and think about it. :)
It's all about the double pointer.
Why isn't the new value showing? Because you never modified the contents of the object which you initialized as nil. To do that you would have needed to use a reference to the memory location where the nil value was stored (a pointer to the pointer), not just a copy of the nil value (which is what you are passing using the single pointer).
This is a really common thing to do in C where some of the output of a function is "returned" through a pointer in the input arguments.
This is how the C language works. You might like to take a look at this question and answer:
C double pointer, which is basically the same thing in a different guise.
Well error that somemethodWithError has is different pointer which is containing the address of the error from where someMethodWithError is get called. so when you assign any object to error pointer of someMethodWithError it will not reflect in the error pointer of method from where it is called
it's like
NSError * error1 = obj1;
NSError * error2 = error1;
and afterward if you assign any new object to error2 like
error2 = obj2;
it will not change the pointer value of error1 and it will still point to obj1

Syntax of ** in objective C

Simple question. What is the meaning of the double asterisk in the interface below? It's not an NSError pointer, but what?
- (BOOL)checkResourceIsReachableAndReturnError:(NSError **)error
It's a pointer to a pointer to an NSError. It's used as an "out parameter" -- or you may want to think of it as a pointer to an NSError instance, considering an NSObject instance is always a pointer.
You use it like this:
NSError * outError = nil; << reserve place and a name for the out parameter
// pass the error so the client may return it to you:
BOOL ret = [obj checkResourceIsReachableAndReturnError:&outError];
if (nil != outError) { << the error was set in checkResourceIsReachableAndReturnError:
NSLog(#"Error: %#", outError); << log it
}
…
On the implementation side, it looks like this:
- (BOOL)checkResourceIsReachableAndReturnError:(NSError**)outError
{
if (self.isThingReachable) {
// okay - the caller may not be interested in the error, test it:
if (0 != outError) {
// they are interested this time!
*outError = [NSError errorWithBlahBlahBlah];
}
return NO;
}
return YES;
}
You call it by sending a reference to an NSError*, like so:
NSError *myError = NULL;
[foo checkResourceIsReachableAndReturnError:&myError];
if (myError) {
/* look through info, handle error, etc. */
}
The -checkResourceIsReachableAndReturnError: method can modify the pointer myError, including allocating space for a new NSError. This allows you to easily check if there was an error, by simply checking if myError is non-NULL.
Check out this blog post: "Using NSError To Great Effect" and pay particular attention to the section "Passing Pointers to Pointers."
Also the wiki page on pointers has a section on "Multiple Indirection"