-->

ARC and autorelease

2020-02-17 08:00发布

问题:

autorelease is used for returned function object so the caller don't take ownership and callee will release the object in the future.

However, ARC is capable to count ownership of caller and release it after use, that is, it can behavior just like Smart Pointer in C++. With ARC, it can get rid of autorelease because autorelease is non-deterministic.

The reason I ask for this question is that I do see the returned object calls dealloc earlier in ARC than non-ARC code. This leads me to think that ARC can behvior like Smart Pointer and can make autorelease useless. Is it true or possible? The only thing I can think about autorelease usefullness is in multip-thread or network code because it may be not easier to count the ownership when the object is passing around.

Thanks for your thoughts.

Here is new edit to make thing clear:

with autorelease

+ (MyClass*) myClass
{
    return [[[MyCClass alloc] init] autorelease];
}

- doSomething
{
   MyClass *obj = [MyClass myClass];
}

With ARC:

+ (MyClass*) myClass
{
    return [[MyCClass alloc] init]; // no autorelease
}

- doSomething
{
   MyClass *obj = [MyClass myClass];
   // insert [obj release]
}

So, we really don't need autorelease.

回答1:

Autorelease as a mechanism is still used by ARC, furthermore ARC compiled-code is designed to interoperate seamlessly with MRC compiled-code so the autorelease machinery is around.

First, don't think in terms of reference counts but in terms of ownership interest - as long as there is a declared ownership interest in an object then the object lives, when there is no ownership interest it is destroyed. In MRC you declare ownership interest by using retain, or by creating a new object; and you relinquish ownership interest by using release.

Now when a callee method creates an object and wishes to return it to its caller the callee is going away so it needs to relinquish ownership interest, and so the caller needs to declare its ownership interest or the object may be destroyed. But there is a problem, the callee finishes before the caller receives the object - so when the caller relinquishes its ownership interest the object may be destroyed before the caller has a chance to declare its interest - not good.

Two solutions are used to address this:

1) The method is declared to transfer ownership interest in its return value from the callee to the caller - this is the model used for init, copy, etc. methods. The callee never notifies it is relinquishing its ownership interest, and the callee never declares ownership interest - by agreement the caller just takes over the ownership interest and the responsibility of relinquishing it later.

2) The method is declared to return a value in which the caller has no ownership interest, but which someone else will maintain an ownership interest in for some short period of time - usually until the end of the current run loop cycle. If the caller wants to use the return value longer than that is must declare its own ownership interest, but otherwise it can rely on someone else having an ownership interest and hence the object staying around.

The question is who can that "someone" be who maintains the ownership interest? It cannot be the callee method as it is about to go away. Enter the "autorelease pool" - this is just an object to which anybody can transfer an ownership interest to so the object will stay around for a while. The autorelease pool will relinquish its ownership interest in all the objects transferred to it in this way when instructed to do so - usually at the end of the current run loop cycle.

Now if the above makes any sense (i.e. if I explained it clearly), you can see that method (2) is not really required as you could always use method (1); but, and its a crucial but, under MRC that is a lot more work for the programmer - every value received from a method comes with an ownership interest which must be managed and relinquished at some point - generate a string just to output it? Well you then need to relinquish your interest in that temporary string... So (2) makes life a lot easier.

One the other hand computers are just fast idiots, and counting things and inserting code to relinquish ownership interest on behalf of the intelligent programmers is something they are well suited to. So ARC doesn't need the auto release pool. But it can make things easier and more efficient, and behind the scenes ARC optimises its use - look at the assembler output in Xcode and you'll see calls to routines with name similar to "retainAutoreleasedReturnValue"...

So you are right, its not needed, however it is still useful - but under ARC you can (usually) forget it even exists.

HTH more than it probably confuses!



回答2:

Difference between ARC and autorelease explained in code :

ARC :

-somefunc {
  id obj = [NSArray array];
  NSLog(@"%@", obj);
  // ARC now calls release for the first object

  id obj2 = [NSArray array];
  NSLog(@"%@", obj2);
  // ARC now calls release for the second object
}

Autorelease :

-somefunc {
  id obj = [NSArray array];
  NSLog(@"%@", obj);

  id obj2 = [NSArray array];
  NSLog(@"%@", obj2);
}
// Objects are released some time after this

Basically ARC works once a variable isn't used anymore in a scope, while autorelease waits until it reaches the main loop and then calls release on all objects in the pool. ARC is used inside the scope, autorelease is used outside the scope of the function.



回答3:

autorelease is used for returned function object so the caller don't take ownership and callee will release the object in the future.

If autoreleased, it will be added to the autorelease pool. When the autorelease pool is drained, the deferred release will be performed. a function/method does not need to return an autoreleased object (e.g. it could be an ivar which did not receive a retain/autorelease cycle).

However, ARC is capable to count ownership of caller and release it after use, that is, it can behavior just like Smart Pointer in C++. With ARC, it can get rid of autorelease because autorelease is non-deterministic.

It has the potential to. There is no guarantee. The biggest 'problem' here is that the compiler does not know/care the memory mechanics of the returned object of an arbitrary call. It cannot assume how an object is returned because ARC is a new addition which predates MRC. This is important because it makes ARC programs compatible with programs which use manual retain/release. For example, Foundation.framework may use ARC, or it may use MRC, or it may use both. It may also call into APIs which were built using older toolchains. So this has the benefit of keeping a ton of existing code usable.

The reason I ask for this question is that I do see the returned object calls dealloc earlier in ARC than non-ARC code.

There's an optional way to return an object -- see CRD's answer (+1) about assembly and the calls the compiler inserts to perform reference count operations e.g. retainAutoreleasedReturnValue.

In any event, there is no guarantee that lifetimes will always be reduced in ARC. A programmer who understands execution of their program can minimize lifetimes and ref count operations because ARC has stricter lifetime and ownership requirements.

This leads me to think that ARC can behvior like Smart Pointer and can make autorelease useless. Is it true or possible?

In theory, I don't see why autorelease pools could not be done away with for a new system. However, I think there's too much existing code that relies on autorelease pools to lift that restriction -- I think they would need to phase in a new executable format (as was the case with ObjC Garbage Collection) and review a ton of existing APIs and programs for such a significant transition to succeed. Also, a few APIs would probably just need to be removed. APIs may need some strengthening concerning ownership to accomplish this, but most of that is complete in programs which have already been migrated to ARC. Heck, even the compiler could (be extended to) internally use a form of smart pointers for passing and returning objc types and autorelease pools could be eliminated in such a system. Again, that would require a lot of code to be migrated. So such an upgrade would be like an ARC V2.

The only thing I can think about autorelease usefullness is in multip-thread or network code because it may be not easier to count the ownership when the object is passing around.

Not an issue - autorelease pools are thread local. I don't see an issue beyond that in such a system (unless you are relying on a race condition, which is obviously a bad idea).



回答4:

autorelease is still used under ARC. ARC just makes the call for you and is clever about short-circuiting it. Here is a demonstration of exactly how that works, which I'll copy here in case that blog post ever disappears; all due credit to Matt Galloway.

So consider the following method:

void foo() {
    @autoreleasepool {
        NSNumber *number = [NSNumber numberWithInt:0];
        NSLog(@"number = %p", number);
    }
}

This is entirely contrived, of course, but it should let us see what’s going on. In non-ARC land we would assume here that number would be allocated inside numberWithInt: and returned autoreleased. So when the autorelease pool is next drained, it will be released. So let’s see if that’s what happened (as usual, this is ARMv7 instructions):

    .globl  _foo
    .align  2
    .code   16
    .thumb_func     _foo
_foo:
    push    {r4, r7, lr}
    add     r7, sp, #4
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    mov     r1, r0
    movw    r0, :lower16:(L__unnamed_cfstring_-(LPC0_2+4))
    movt    r0, :upper16:(L__unnamed_cfstring_-(LPC0_2+4))
LPC0_2:
    add     r0, pc
    blx     _NSLog
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    pop     {r4, r7, pc}

Well, yes. That’s exactly what’s happening. We can see the call to push an autorelease pool then a call to numberWithInt: then a call to pop an autorelease pool. Exactly what we’d expect. Now let’s look at the exact same code compiled under ARC:

    .globl  _foo
    .align  2
    .code   16
    .thumb_func     _foo
_foo:
    push    {r4, r5, r7, lr}
    add     r7, sp, #8
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
LPC0_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    @ InlineAsm Start
    mov     r7, r7          @ marker for objc_retainAutoreleaseReturnValue
    @ InlineAsm End
    blx     _objc_retainAutoreleasedReturnValue
    mov     r5, r0
    movw    r0, :lower16:(L__unnamed_cfstring_-(LPC0_2+4))
    movt    r0, :upper16:(L__unnamed_cfstring_-(LPC0_2+4))
    mov     r1, r5
LPC0_2:
    add     r0, pc
    blx     _NSLog
    mov     r0, r5
    blx     _objc_release
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    pop     {r4, r5, r7, pc}

Notice the calls to objc_retainAutoreleasedReturnValue and objc_release. What’s happening there is that ARC has determined for us that it doesn’t really need to worry about the autorelease pool that’s in place, because it can simply tell the autorelease to not happen (with the call to objc_retainAutoreleasedReturnValue) and then release the object later itself. This is desirable as it means the autorelease logic doesn’t have to happen.

Note that the autorelease pool is still required to be pushed and popped because ARC can’t know what’s going on in the calls to numberWithInt: and NSLog to know if objects will be put into the pool there. If it did know that they didn’t autorelease anything then it could actually get rid of the push and pop. Perhaps that kind of logic will come in future versions although I’m not quite sure how the semantics of that would work though.

Now let’s consider another example which is where we want to use number outside of the scope of the autorelease pool block. This should show us why ARC is a wonder to work with. Consider the following code:

void bar() {
    NSNumber *number;
    @autoreleasepool {
        number = [NSNumber numberWithInt:0];
        NSLog(@"number = %p", number);
    }
    NSLog(@"number = %p", number);
}

You might be (correctly) thinking that this is going to cause problems even though it looks perfectly innocuous. It’s a problem because number will be allocated inside the autorelease pool block, will be deallocated when the autorelease pool pops but is then used after it’s been deallocated. Uh oh! Let’s see if we’re right by compiling it without ARC enabled:

    .globl  _bar
    .align  2
    .code   16
    .thumb_func     _bar
_bar:
    push    {r4, r5, r6, r7, lr}
    add     r7, sp, #12
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    movw    r6, :lower16:(L__unnamed_cfstring_-(LPC1_2+4))
    movt    r6, :upper16:(L__unnamed_cfstring_-(LPC1_2+4))
LPC1_2:
    add     r6, pc
    mov     r5, r0
    mov     r1, r5
    mov     r0, r6
    blx     _NSLog
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    mov     r0, r6
    mov     r1, r5
    blx     _NSLog
    pop     {r4, r5, r6, r7, pc}

Obviously no calls to retain, release or autorelease as we’d expect since we haven’t made any explicitly and we’re not using ARC. We can see here that it’s been compiled exactly as we’d expect from our reasoning before. So let’s see what it looks like when ARC gives us a helping hand:

    .globl  _bar
    .align  2
    .code   16
    .thumb_func     _bar
_bar:
    push    {r4, r5, r6, r7, lr}
    add     r7, sp, #12
    blx     _objc_autoreleasePoolPush
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    movs    r2, #0
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
    mov     r4, r0
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_0:
    add     r1, pc
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
LPC1_1:
    add     r0, pc
    ldr     r1, [r1]
    ldr     r0, [r0]
    blx     _objc_msgSend
    @ InlineAsm Start
    mov     r7, r7          @ marker for objc_retainAutoreleaseReturnValue
    @ InlineAsm End
    blx     _objc_retainAutoreleasedReturnValue
    movw    r6, :lower16:(L__unnamed_cfstring_-(LPC1_2+4))
    movt    r6, :upper16:(L__unnamed_cfstring_-(LPC1_2+4))
LPC1_2:
    add     r6, pc
    mov     r5, r0
    mov     r1, r5
    mov     r0, r6
    blx     _NSLog
    mov     r0, r4
    blx     _objc_autoreleasePoolPop
    mov     r0, r6
    mov     r1, r5
    blx     _NSLog
    mov     r0, r5
    blx     _objc_release
    pop     {r4, r5, r6, r7, pc}

Round of applause for ARC please! Notice that it’s realised we’re using number outside of the scope of the autorelease pool block so it’s retained the return value from numberWithInt: just as it did before, but this time it’s placed the release at the end of the bar function rather than before the autorelease pool is popped. That will have saved us a crash in some code that we might have thought was correct but actually had a subtle memory management bug.



回答5:

However, ARC is capable to count ownership of caller and release it after use, that is, it can behavior just like Smart Pointer in C++. With ARC, it can get rid of autorelease because autorelease is non-deterministic.

You are confusing ARC with reference counting. Objective-C has always relied on reference counting for memory management. ARC continues this tradition and simply eliminates the need for the programmer to manually insert appropriate calls to -retain, -release, and -autorelease. Under ARC the compiler inserts these calls for you, but the reference counting mechanism remains the same as it has always been.

ARC does not eliminate the need for autorelease, but it may be able to avoid it in situations where a human would typically have used it.