[Objc翻译]看看 ARC 的引擎盖下 - 第 1 集
本文由 简悦 SimpRead转码, 原文地址 www.galloway.me.uk
在 Twitter 上与 @jacobrelkin 对话之后,我决定写一篇关于 ARC 如何工作的小文章......。
在与@jacobrelkin进行Twitter对话之后,我决定写一篇关于 ARC 如何在引擎盖下工作以及如何查看它在做什么的文章。在这篇文章中,我将解释 ARC 如何相应地添加retain
、release
和autorelease
调用。
首先,我们将这样定义一个类:
#import <Foundation/Foundation.h>
@interface ClassA : NSObject
@property (nonatomic, retain) NSNumber *foo;
@end
@implementation ClassA
@synthesize foo;
- (void)changeFooDirect:(NSNumber*)inFoo {
foo = inFoo;
}
- (void)changeFooSetter:(NSNumber*)inFoo {
self.foo = inFoo;
}
- (NSNumber*)newNumber {
return [[NSNumber alloc] initWithInt:10];
}
- (NSNumber*)getNumber {
return [[NSNumber alloc] initWithInt:10];
}
@end
这概述了 ARC 的几个重要方面,包括直接访问 ivars 与使用设置器的区别,以及 ARC 在从方法返回对象时如何根据方法名称添加自动释放调用。
让我们先看看直接访问 ivars 与使用设置器的区别。如果我们编译该代码并查看程序集,就能了解发生了什么。我决定使用 ARMv7,因为它比 x86 更容易理解发生了什么(反正我是这么认为的!)。我们可以使用-fobjc-arc
和-fno-objc-arc
编译器选项打开或关闭 ARC。在这些示例中,我使用了优化级别 3,这意味着编译器也会删除多余的代码,而我们对这些代码并不感兴趣,它们会堵塞理解(读者可以尝试不使用任何优化,看看会是什么样子)。
因此,为了不使用 ARC 进行编译,我使用了以下命令:
$ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/clang -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -arch armv7 -fno-objc-arc -O3 -S -o - test-arc.m
让我们看看 changeFooDirect:
和 changeFooSetter:
:
.align 2
.code 16
.thumb_func "-[ClassA changeFooDirect:]"
"-[ClassA changeFooDirect:]":
movw r1, :lower16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
movt r1, :upper16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
LPC0_0:
add r1, pc
ldr r1, [r1]
str r2, [r0, r1]
bx lr
.align 2
.code 16
.thumb_func "-[ClassA changeFooSetter:]"
"-[ClassA changeFooSetter:]":
push {r7, lr}
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
mov r7, sp
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
LPC1_0:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
pop {r7, pc}
接下来,让我们看看启用 ARC 后的效果。为此,我使用了以下命令:
$ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/clang -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -arch armv7 -fobjc-arc -O3 -S -o - test-arc.m
同样,我们现在感兴趣的是 changeFooDirect:
和 changeFooSetter:
:
.align 2
.code 16
.thumb_func "-[ClassA changeFooDirect:]"
"-[ClassA changeFooDirect:]":
push {r7, lr}
movw r1, :lower16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
mov r7, sp
movt r1, :upper16:(_OBJC_IVAR_$_ClassA.foo-(LPC0_0+4))
LPC0_0:
add r1, pc
ldr r1, [r1]
add r0, r1
mov r1, r2
blx _objc_storeStrong
pop {r7, pc}
.align 2
.code 16
.thumb_func "-[ClassA changeFooSetter:]"
"-[ClassA changeFooSetter:]":
push {r7, lr}
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
mov r7, sp
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
LPC1_0:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
pop {r7, pc}
我们一眼就能看出这里的区别。changeFooSetter:
完全相同,而 changeFooDirect:
只调用了一次 objc_storeStrong
,就发生了变化。这就是有趣的地方。如果我们查看一下 LLVM 文档,就会发现它是通过释放旧值并保留新值来进行标准的变量交换。而在非 ARC 版本中,ivar 只是交换,没有任何保留或释放。这正是我们所期望的!感谢 ARC!
现在是更有趣的部分,"newNumber "与 "getNumber"。在非 ARC 环境中,这些方法都返回保留数为 1 的 NSNumber
对象,即调用者拥有这些对象。这对 newNumber
来说是正确的,但根据 Cocoa 的命名约定,对 getNumber
来说就不正确了。我们希望在从getNumber
返回时看到一个autorelease
调用。让我们看看没有 ARC 的代码是什么样的:
.align 2
.code 16
.thumb_func "-[ClassA newNumber]"
"-[ClassA newNumber]":
push {r7, lr}
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
mov r7, sp
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
movw r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
movt r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
LPC2_0:
add r1, pc
LPC2_1:
add r0, pc
ldr r1, [r1]
ldr r0, [r0]
blx _objc_msgSend
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
movs r2, #10
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
LPC2_2:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
pop {r7, pc}
.align 2
.code 16
.thumb_func "-[ClassA getNumber]"
"-[ClassA getNumber]":
push {r7, lr}
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
mov r7, sp
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
movw r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
movt r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
LPC3_0:
add r1, pc
LPC3_1:
add r0, pc
ldr r1, [r1]
ldr r0, [r0]
blx _objc_msgSend
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
movs r2, #10
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
LPC3_2:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
pop {r7, pc}
现在使用 ARC:
.align 2
.code 16
.thumb_func "-[ClassA newNumber]"
"-[ClassA newNumber]":
push {r7, lr}
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
mov r7, sp
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC2_0+4))
movw r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
movt r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC2_1+4))
LPC2_0:
add r1, pc
LPC2_1:
add r0, pc
ldr r1, [r1]
ldr r0, [r0]
blx _objc_msgSend
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
movs r2, #10
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC2_2+4))
LPC2_2:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
pop {r7, pc}
.align 2
.code 16
.thumb_func "-[ClassA getNumber]"
"-[ClassA getNumber]":
push {r7, lr}
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
mov r7, sp
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC3_0+4))
movw r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
movt r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC3_1+4))
LPC3_0:
add r1, pc
LPC3_1:
add r0, pc
ldr r1, [r1]
ldr r0, [r0]
blx _objc_msgSend
movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
movs r2, #10
movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC3_2+4))
LPC3_2:
add r1, pc
ldr r1, [r1]
blx _objc_msgSend
blx _objc_autorelease
pop {r7, pc}
再看看唯一的不同之处--在 getNumber:
中向 objc_autorelease
调用了一个 blx
(这是一个方法调用)。这正是 ARC 所期望的,因为它注意到方法名不是以 new
或 copy
开头,而且它知道在返回时 NSNumber
的保留数是 1,所以它添加了一个 autorelease
调用。非常好!
以上只是展示了 ARC 在两种情况下的工作原理,我希望这能启发读者自己去探究 ARC 的工作原理,而不是想当然地认为它是正确的。作为程序员,了解工具的工作原理非常重要。