how to create an autorelease object

136 Views Asked by At

Is this method create an autorelease object?

- (instancetype)autoreleasePerson {
    return [[Person alloc] init];
}

I created an Command Line Tool Project to test this:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        {
            [Person autoreleasePerson];
            
        }
        NSLog(@"did out scope");
        
        NSLog(@"will out autoreleasepool");
    }
    NSLog(@"did out autoreleasepool");
    return 0;
}

And the output is:

2022-02-04 23:22:23.224298+0800 MyTest[8921:4007144] did out scope
2022-02-04 23:22:23.224771+0800 MyTest[8921:4007144] will out autoreleasepool
2022-02-04 23:22:23.224876+0800 MyTest[8921:4007144] -[Person dealloc]
2022-02-04 23:22:23.224948+0800 MyTest[8921:4007144] did out autoreleasepool

The person instance will dealloc when the autoreleasepool drains!

But when I use the same Person class in my iOS APP project:

- (void)viewDidLoad {
    [super viewDidLoad];
    {
        [Person autoreleasePerson];
    }
    NSLog(@"out scope");
}

The output is:

2022-02-04 23:28:13.992969+0800 MyAppTest[9023:4011490] -[Person dealloc] <Person: 0x600001fe8ff0>
2022-02-04 23:28:13.993075+0800 MyAppTest[9023:4011490] out scope

The person instance released once out of scope!

Why is this so?

2

There are 2 best solutions below

2
battlmonstr On

It looks like on macOS the default behaviour is to autorelease return values, except for cases where the method name starts with "new", "init" or "copy":

+ (Person *)createPerson {
    return [Person new]; // autorelease & return
}
+ (Person *)newPerson {
    return [Person new]; // direct return
}

To control this behaviour apply a compiler attribute:

+ (Person *)createPerson __attribute__((ns_returns_retained)) {
    return [Person new]; // direct return
}
+ (Person *)newPerson __attribute__((ns_returns_not_retained)) {
    return [Person new]; // autorelease & return
}

To check whether a call to objc_autoreleaseReturnValue was added by the compiler, enable Debug -> Debug Workflow -> Always Show Disassembly, and put a breakpoint inside these methods on return lines. A call to objc_autoreleaseReturnValue should be visible then:

enter image description here

See ARC reference - Retained return values

0
newacct On

Both of the results are valid. You should never assume that there is an autorelease in ARC. See the section "Unretained return values" in the ARC specification:

A method or function which returns a retainable object type but does not return a retained value must ensure that the object is still valid across the return boundary.

When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool.

So maybe it's autoreleased, and maybe not (i.e. maybe ARC optimizes it out).

Here, ARC will call objc_autoreleaseReturnValue() when returning from autoreleasePerson, because +alloc returns a retained reference, but autoreleasePerson returns a non-retained reference. What objc_autoreleaseReturnValue() does is check to see if the result of the return will be passed to objc_retainAutoreleasedReturnValue() in the calling function frame. If so, it can skip both the autorelease in the called function, and the retain in the calling function (since they "cancel out"), and hand off ownership directly into a retained reference in the calling function.

objc_retainAutoreleasedReturnValue() is called when ARC will retain the result of a function call. Now, I don't know why in this case calling [Person autoreleasePerson]; will involve a retain of the result, since the result is unused. Perhaps the compiler is treating it as Person temp = [Person autoreleasePerson];, and thus retains and then releases it. This may seem unnecessary, but it is valid for ARC to do it this way. And if ARC does happen to treat it this way internally, then the optimization described above can skip both the autorelease and retain, and it will be simply released in the calling function. Maybe it's doing this in one of your cases and not the other. Who knows why? But my point is that both are valid.

See this article for a more detailed explanation.