18

autorelease 用于返回的函数对象,因此调用者不拥有所有权,被调用者将来会释放该对象。

然而,ARC 能够计算调用者的所有权并在使用后释放它,也就是说,它的行为就像 C++ 中的智能指针一样。使用 ARC,它可以摆脱自动释放,因为自动释放是不确定的。

我问这个问题的原因是我确实看到返回的对象在 ARC 中调用 dealloc 比非 ARC 代码更早。这使我认为 ARC 可以像 Smart Pointer 一样运行,并且可以使自动释放无用。这是真的还是可能的?我唯一能想到的自动释放有用性是在多线程或网络代码中,因为在对象传递时计算所有权可能并不容易。

谢谢你的想法。

这是新的编辑以使事情变得清晰:

带自动释放

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

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

使用 ARC:

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

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

所以,我们真的不需要自动释放。

4

5 回答 5

26

ARC仍然使用Autorelease 作为一种机制,此外,ARC 编译代码旨在与 MRC 编译代码无缝互操作,因此自动释放机制就在身边。

首先,不要考虑引用计数,而是考虑所有权利益——只要一个对象声明了所有权利益,那么该对象就会存在,当没有所有权利益时,它就会被销毁。在 MRC 中,您通过使用retain或创建新对象来声明所有权权益;并且您通过使用release.

现在,当被调用者方法创建一个对象并希望将其返回给调用者时,被调用者将离开,因此它需要放弃所有权权益,因此调用者需要声明其所有权权益,否则该对象可能会被销毁。但是有一个问题,被调用者在调用者收到对象之前完成 - 所以当调用者放弃其所有权权益时,对象可能在调用者有机会声明其权益之前被销毁 - 不好。

有两种解决方案用于解决此问题:

1) 声明该方法以其返回值中的所有权权益从被调用者转移到调用者 - 这是用于 、 等方法的init模型copy。被调用者从不通知它正在放弃其所有权权益,并且被调用者从不声明所有权权益 - 通过协议调用者只是接管所有权权益和稍后放弃它的责任。

2) 该方法被声明为返回一个调用者没有所有权权益的值,但其他人将在短时间内保持所有权权益 - 通常直到当前运行循环周期结束。如果调用者想要使用返回值的时间超过它必须声明自己的所有权权益,否则它可以依赖拥有所有权权益的其他人,因此该对象会保留。

问题是那个“某人”可以是谁维护所有权利益?它不能是被调用者方法,因为它即将消失。进入“自动释放池”——这只是一个任何人都可以向其转移所有权权益的对象,因此该对象将保留一段时间。当指示这样做时,自动释放池将放弃其在以这种方式转移给它的所有对象的所有权权益 - 通常在当前运行循环周期结束时。

现在,如果上述内容有任何意义(即如果我解释清楚),您可以看到方法(2)并不是真正需要的,因为您总是可以使用方法(1);但是,这是一个至关重要的但是,在 MRC 下,程序员需要做更多的工作——从方法接收到的每个值都带有所有权利益,必须在某个时候进行管理和放弃——生成一个字符串只是为了输出它?那么你需要放弃你对那个临时字符串的兴趣......所以(2)让生活变得更轻松。

另一方面,计算机只是快速的白痴,代表聪明的程序员计算事物并插入代码以放弃所有权利益是他们非常适合的事情。所以ARC不需要自动释放池。但它可以让事情变得更容易和更高效,并且在幕后 ARC 优化了它的使用 - 查看 Xcode 中的汇编器输出,您会看到对名称类似于“retainAutoreleasedReturnValue”的例程的调用......

所以你是对的,它不需要,但它仍然有用 - 但在 ARC 下你可以(通常)忘记它甚至存在。

HTH 比它可能更令人困惑!

于 2013-07-12T04:09:01.610 回答
6

autorelease 用于返回的函数对象,因此调用者不拥有所有权,被调用者将来会释放该对象。

如果自动释放,它将被添加到自动释放池中。当自动释放池耗尽时,将执行延迟释放。一个函数/方法不需要返回一个自动释放的对象(例如,它可能是一个没有收到保留/自动释放周期的 ivar)。

然而,ARC 能够计算调用者的所有权并在使用后释放它,也就是说,它的行为就像 C++ 中的智能指针一样。使用 ARC,它可以摆脱自动释放,因为自动释放是不确定的。

它有潜力。没有任何保证。这里最大的“问题”是编译器不知道/关心任意调用的返回对象的内存机制。它不能假设对象是如何返回的,因为 ARC 是早于 MRC 的新增内容。这很重要,因为它使 ARC 程序与使用手动保留/释放的程序兼容。例如,Foundation.framework 可能使用 ARC,也可能使用 MRC,或者两者都使用。它还可能调用使用旧工具链构建的 API。所以这有利于保持大量现有代码可用。

我问这个问题的原因是我确实看到返回的对象在 ARC 中调用 dealloc 比非 ARC 代码更早。

有一种返回对象的可选方式——参见 CRD 的关于程序集的答案 (+1) 以及编译器插入以执行引用计数操作的调用,例如retainAutoreleasedReturnValue.

无论如何,不​​能保证 ARC 中的生命周期总是会减少。了解程序执行的程序员可以最小化生命周期和引用计数操作,因为 ARC 具有更严格的生命周期和所有权要求。

这使我认为 ARC 可以像 Smart Pointer 一样运行,并且可以使自动释放无用。这是真的还是可能的?

理论上,我不明白为什么不能为新系统取消自动释放池. 但是,我认为有太多现有代码依赖于自动释放池来解除该限制——我认为他们需要逐步采用新的可执行格式(就像 ObjC 垃圾收集的情况一样)并审查大量现有 API 和如此重大的转型成功的计划。此外,可能只需要删除一些 API。API 可能需要对所有权进行一些加强才能实现这一点,但其中大部分在已经迁移到 ARC 的程序中是完整的。哎呀,甚至编译器也可以(扩展为)在内部使用一种智能指针形式来传递和返回 objc 类型,并且可以在这样的系统中消除自动释放池。同样,这将需要迁移大量代码。所以这样的升级就像 ARC V2。

我唯一能想到的自动释放有用性是在多线程或网络代码中,因为在对象传递时计算所有权可能并不容易。

不是问题 - 自动释放池是线程本地的。在这样的系统中,我没有看到除此之外的问题(除非您依赖于竞争条件,这显然是一个坏主意)。

于 2013-07-12T05:39:13.333 回答
5

代码中解释的 ARC 和 autorelease 之间的区别:

弧:

-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
}

自动释放:

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

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

基本上,一旦变量不再在作用域中使用,ARC 就会起作用,而 autorelease 会一直等到它到达主循环,然后调用release池中的所有对象。ARC在作用域内使用,autorelease在函数作用域外使用。

于 2013-07-11T19:03:18.793 回答
5

autorelease在 ARC 下仍然使用。ARC 只是为您打电话,并且很聪明地将其短路。这是它如何工作的演示,我将在此处复制,以防博客文章消失;全部归功于马特·加洛韦。

所以考虑以下方法:

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

当然,这完全是人为的,但它应该让我们看看发生了什么。在非 ARC 领域,我们假设该数字将在 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 的调用中发生了什么,以了解对象是否会被放入池中。如果它确实知道他们没有自动释放任何东西,那么它实际上可以摆脱推送和弹出。也许这种逻辑会出现在未来的版本中,尽管我不太确定它的语义是如何工作的。

现在让我们考虑另一个例子,我们想在自动释放池块范围之外使用数字。这应该向我们展示为什么使用 ARC 是一个奇迹。考虑以下代码:

void bar() {
    NSNumber *number;
    @autoreleasepool {
        number = [NSNumber numberWithInt:0];
        NSLog(@"number = %p", number);
    }
    NSLog(@"number = %p", 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}

显然没有像我们期望的那样调用保留、释放或自动释放,因为我们没有明确地做出任何调用并且我们没有使用 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 函数的末尾而不是之前自动释放池被弹出。这将避免我们在一些我们可能认为是正确的但实际上有一个微妙的内存管理错误的代码中崩溃。

于 2013-07-12T21:04:24.933 回答
2

然而,ARC 能够计算调用者的所有权并在使用后释放它,也就是说,它的行为就像 C++ 中的智能指针一样。使用 ARC,它可以摆脱自动释放,因为自动释放是不确定的。

您将 ARC 与引用计数混淆了。Objective-C 一直依赖引用计数来进行内存管理。ARC 延续了这一传统,简单地消除了程序员手动插入对 、 和 . 的适当调用-retain-release需要-autorelease。在 ARC 下,编译器会为您插入这些调用,但引用计数机制与以往一样。

ARC 并没有消除自动释放的需要,但它可能能够在人类通常使用它的情况下避免它。

于 2013-07-13T15:35:38.503 回答