本文由 简悦 SimpRead 转码, 原文地址 www.galloway.me.uk
本期的 "ARC 引擎盖下的一瞥 "主要介绍新的 @autoreleasepool 指令。LLVM te......
本期 "ARC 引擎盖下的探秘 "主要介绍新的 @autoreleasepool 指令。LLVM告诉我们自动释放池的语义在LLVM 3.0中发生了变化,特别是,我认为看看ARC是如何处理这些的可能会很有趣。
因此,请考虑以下方法:
void foo() {
@autoreleasepool {
NSNumber *number = [NSNumber numberWithInt:0];
NSLog(@"number = %p", number);
}
}
当然,这完全是设计出来的,但我们可以从中了解到发生了什么。在非 ARC 环境中,我们会假设 number 将被分配到 numberWithInt: 中并自动释放。因此,当自动释放池下次被耗尽时,它就会被释放。因此,让我们看看是否发生了这种情况(像往常一样,这是 ARMv7 指令):
.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}
嗯,没错。就是这么回事。我们可以看到先调用推送一个自动释放池,然后调用 numberWithInt: 然后再调用弹出一个自动释放池。这正是我们所期望的。现在让我们看看在 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}
注意对 objc_retainAutoreleasedReturnValue 和 objc_release 的调用。这里发生的事情是,ARC 已经为我们确定,它实际上不需要担心自动释放池,因为它可以简单地告诉自动释放不发生(调用 objc_retainAutoreleasedReturnValue),然后稍后自己释放对象。这样做是可取的,因为这意味着自动释放逻辑不必发生。
请注意,自动释放池仍然需要推送和弹出,因为 ARC 无法知道在调用 numberWithInt: 和 NSLog 时发生了什么,也就无法知道对象是否会被放入池中。如果它知道它们没有自动释放任何东西,那么它就可以真正摆脱推送和弹出。也许这种逻辑会在未来的版本中出现,不过我还不太清楚它的语义是怎样的。
现在让我们考虑另一个例子,即我们想在自动释放池块的范围之外使用 number。这将告诉我们为什么 ARC 是一个神奇的工具。请看下面的代码
void bar() {
NSNumber *number;
@autoreleasepool {
number = [NSNumber numberWithInt:0];
NSLog(@"number = %p", number);
}
NSLog(@"number = %p", number);
}
你可能会(正确地)认为这会带来问题,尽管它看起来完全无害。之所以会有问题,是因为 number 将被分配到自动释放池块中,并在自动释放池弹出时被取消分配,但在取消分配后又被使用。啊哦 让我们在不启用 ARC 的情况下编译它,看看是否正确:
.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}
显然没有调用我们所期望的 retain、release 或 autorelease,因为我们没有明确调用这些函数,也没有使用 ARC。我们可以看到,编译结果与我们之前的推理完全一致。让我们看看 ARC 帮了我们什么忙:
.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}
请为 ARC 鼓掌!请注意,它意识到我们在自动释放池块的范围之外使用了 number 函数,因此它保留了 numberWithInt: 的返回值,就像以前一样,但这次它把释放放在了 bar 函数的末尾,而不是在自动释放池被弹出之前。这就避免了一些我们可能认为正确但实际上存在微妙内存管理 bug 的代码崩溃。