Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 201
jtbandes Avatar asked Jan 07 '13 11:01

jtbandes


1 Answers

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.

like image 107
Tammo Freese Avatar answered Oct 03 '22 07:10

Tammo Freese