ARC 和自动释放

Posted

技术标签:

【中文标题】ARC 和自动释放【英文标题】:ARC and autorelease 【发布时间】:2013-07-10 04:35:05 【问题描述】:

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]

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

【问题讨论】:

自动释放根本不是不确定的。池在每次运行循环的底部向每个包含的对象发送一次release @JoshCaswell 我在自动发布相关代码中看到了一些细微的错误。当代码调用一个对象时,实际上是“释放”,但释放是在每次运行循环的底部结束时完成的。当代码迁移到 ACR 时,它会崩溃。原因是ARC缩短了对象的寿命。 @user2573923 确实如此,但这无关紧要,除非您使用 weak 引用,在使用它们之前,您应该始终将其本地转换为 strong @JoshCaswell 这是我的全部观点,使用弱和强足以摆脱 ARC 的自动释放。这是在 C++ 中完成的。 Objective-C: Why is autorelease (@autoreleasepool) still needed with ARC? 的可能重复项 【参考方案1】:

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

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

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

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

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

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

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

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

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

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

HTH 比它可能令人困惑的要多!

【讨论】:

谢谢。这是非常有帮助的。使用 ARC,我认为您不需要自动释放的第一个解决方案。我检查了 Clang,它说的和你在这里解释的一样。 关于“autorelasepool 块”(@autoreleasepool...)developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…的信息【参考方案2】:

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。

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

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

【讨论】:

谢谢贾斯汀。解释得很好。【参考方案3】:

代码中解释了 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在函数范围外使用。

【讨论】:

我认为 ARC 可以像智能点一样取代自动释放。您不需要自动释放返回的对象,相反,当调用者调用该函数时,ARC 可以保留它。调用者处理完对象后,ARC 将其释放。未来你不需要等待。它是不确定的,而且很糟糕。 @H2CO3 // objects are released some time after this 就足够了,你说呢? @Tommy 是的,没错。或许也可以插入“在运行循环结束时”。 @H2CO3 在运行循环周期结束时释放的唯一池是由运行循环维护的池(基本上只有主线程上的运行循环自动发生)有在许多情况下,您会在 runloop 上下文之外启用池的 -drain 功能。 -1 这在几个方面是不正确的。在第一个示例中,您仍然获得自动释放的对象,ARC 或没有 ARC,因为您调用的是 +array 而不是 +alloc【参考方案4】:

autorelease 在 ARC 下仍然使用。 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);

您可能(正确地)认为这会导致问题 即使它看起来完全无害。这是一个问题,因为 编号将在自动释放池块内分配,将是 当自动释放池弹出时释放,但在它结束后使用 被解除分配。哦哦!让我们通过编译它来看看我们是否正确 未启用 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: 的返回值,就像它一样 之前,但这次将释放放在栏的末尾 函数,而不是在自动释放池弹出之前。那将 为我们节省了一些我们可能认为是的代码崩溃 正确但实际上有一个微妙的内存管理错误。

【讨论】:

根据我对版权法的理解,带有署名的公平使用没有附加任何“这么多”限定词。如果您认为我的归属不够清楚,请提供一个适合您的示例,我将对其进行编辑。如果您认为合理使用受版权保护的材料确实有规定的长度限制,请链接该事实的文档,以便我进行自我教育。 是的,我看到“如果次要用户只复制其预期用途所需的数量”。这里的预期用途是全面回答问题。以上哪一部分显然是不必要的?如果提问者同意,我一定会删除该部分。 嘿。我也通过他的网站给他发了电子邮件。所以我相信我们可以将其保留在马特认为必要的任何编辑上,这样你也会满意吗? 大家好。乔希——谢谢你的指出,但亚历克斯不用担心——我不介意。也许只是一个链接就足够了,而不是复制和粘贴。但是你做了属性,所以一切都很好。很高兴您首先发现这篇文章很有用。乔希 - 感谢您的关注。这让我意识到我需要在我的网站上获得许可证才能明确说明我允许的内容。我会让它完全免费使用,但需要署名。【参考方案5】:

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

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

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

【讨论】:

以上是关于ARC 和自动释放的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ARC 在 iOS 中处理自动释放的对象

ARC模式下操作对象无效,检查对象是否被自动释放掉

自动引用计数

即使在 ARC 下,在 GCD 中使用 Realm 时,我们是不是必须使用显式自动释放池

10.7 上的 ARC 迁移工具给出错误:删除未使用的“自动释放”消息是不安全的

自动释放池页面损坏