欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

[Objc翻译]看看 ARC 的引擎盖下 - 第 1 集

最编程 2024-08-13 15:56:04
...

本文由 简悦 SimpRead转码, 原文地址 www.galloway.me.uk

在 Twitter 上与 @jacobrelkin 对话之后,我决定写一篇关于 ARC 如何工作的小文章......。

在与@jacobrelkin进行Twitter对话之后,我决定写一篇关于 ARC 如何在引擎盖下工作以及如何查看它在做什么的文章。在这篇文章中,我将解释 ARC 如何相应地添加retainreleaseautorelease调用。

首先,我们将这样定义一个类:

#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 所期望的,因为它注意到方法名不是以 newcopy 开头,而且它知道在返回时 NSNumber 的保留数是 1,所以它添加了一个 autorelease 调用。非常好!

以上只是展示了 ARC 在两种情况下的工作原理,我希望这能启发读者自己去探究 ARC 的工作原理,而不是想当然地认为它是正确的。作为程序员,了解工具的工作原理非常重要。