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.