ARC和自动释放

时间:2013-07-11 18:58:41

标签: ios objective-c memory-management automatic-ref-counting reference-counting

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

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

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

感谢您的想法。

这是一个新的编辑,使事情清楚:

with autorelease

+ (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]
}

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

5 个答案:

答案 0 :(得分:24)

作为一种机制的自动释放仍由ARC使用,而ARC编译代码旨在与MRC编译代码无缝互操作,因此自动释放机制就在附近。

首先,不要考虑参考计数而是考虑所有者权益 - 只要对象中有声明的所有者权益,那么对象就存在,当没有所有者权益时,它就会被销毁。在MRC中,您可以使用retain或通过创建新对象来声明所有者权益;并且您使用release放弃了所有者权益。

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

使用两种解决方案来解决这个问题:

1)声明该方法传输所有者对其从被调用者到调用者的返回值的兴趣 - 这是用于initcopy等的模型。 方法。被叫方从未通知它放弃其所有者权益,并且被叫方从未声明所有者权益 - 通过协议,来电者只需接管所有权权益并随后放弃它的责任。

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

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

现在如果上述内容有意义(即如果我清楚地解释了),你可以看到方法(2)并不是真的需要,因为你总是可以使用方法(1); 但是,它是一个至关重要的但是,在MRC下对程序员来说是更多的工作 - 从方法中获得的每个价值都带有必须管理的所有者权益,在某些时候放弃 - 生成一个字符串只是为了输出它?那么你需要放弃你对那个临时字符串的兴趣...所以(2)让生活变得更容易。

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

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

HTH可能会让人感到困惑!

答案 1 :(得分:5)

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

ARC:

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

Autorelease:

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

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

基本上,只要变量在范围内不再使用,ARC就会工作,而自动释放会等到它到达主循环,然后在池中的所有对象上调用release。 ARC使用范围内,自动释放使用范围内。

答案 2 :(得分:5)

  

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

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

  

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

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

  

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

有一种可选的返回对象的方法 - 请参阅CRD关于汇编的答案(+1)以及编译器插入的调用以执行引用计数操作,例如retainAutoreleasedReturnValue

无论如何,无法保证ARC中的生命周期总是会减少。理解程序执行的程序员可以最大限度地缩短生命周期并重新计算操作,因为ARC具有更严格的生命周期和所有权要求。

  

这让我认为ARC可以像Smart Pointer一样,并且可以使autorelease无用。这是真的还是可能的?

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

  

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

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

答案 3 :(得分:3)

ARC仍然使用

autorelease。 ARC只是为你打电话,并且很聪明地将它短路。 Here is a demonstration究竟是如何工作的,我将在这里复制以防博客文章消失;所有应归功于Matt Galloway。

  

请考虑以下方法:

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

您可能(正确地)认为这会导致问题   即使它看起来完全无害。这是一个问题,因为   数字将在autorelease池块内分配,将是   当自动释放池弹出但在其之后使用时释放   被解除分配。哦哦!让我们看看我们是否正确编译它   没有启用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的掌声!请注意,它已经实现了   使用自动释放池块范围之外的数字   它保留了numberWithInt的返回值:就像它一样   之前,但是这次它将释放放在栏的末尾   函数而不是弹出自动释放池之前的函数。那会的   我们可能已经想到了一些代码中的崩溃   正确,但实际上有一个微妙的内存管理错误。

答案 4 :(得分:1)

  

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

您将ARC与引用计数混淆。 Objective-C一直依赖于内存管理的引用计数。 ARC延续了这一传统,并且无需程序员手动将适当的调用插入-retain-release-autorelease。在ARC下,编译器会为您插入这些调用,但引用计数机制仍然与以前一样。

ARC确实消除了对自动释放的需求,但在人们通常会使用它的情况下,它可能能够避免它。