ARC和自动释放

use*_*923 13 memory-management reference-counting objective-c ios automatic-ref-counting

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

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

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

谢谢你的想法.

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

与自动释放

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

- doSomething
{
   MyClass *obj = [MyClass myClass];
}
Run Code Online (Sandbox Code Playgroud)

使用ARC:

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

- doSomething
{
   MyClass *obj = [MyClass myClass];
   // insert [obj release]
}
Run Code Online (Sandbox Code Playgroud)

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

CRD*_*CRD 24

自动释放作为一种机制仍使用由ARC,此外ARC编译代码被设计成自动释放机械是围绕与MRC编译代码无缝互操作.

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

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

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

1)该方法被声明为传递从被叫方来电者在其返回值的股权-这是用于模型init,copy等方法.被叫方从未通知它放弃其所有者权益,并且被叫方从未宣布所有者权益 - 通过协议,来电者只需接管所有者权益并承担以后放弃所有权的责任.

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

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

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

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

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

HTH超过它可能会混淆!


Tom*_*rdt 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
}
Run Code Online (Sandbox Code Playgroud)

自动释放:

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

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

基本上ARC只在一个变量不再使用的范围内工作,而autorelease等待它到达主循环然后调用release池中的所有对象.ARC被利用的范围内,自动释放使用外部函数的范围.


jus*_*tin 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.

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

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


Ale*_*ylo 5

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

因此,请考虑以下方法:

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

当然,这完全是人为的,但它应该让我们看看发生了什么。在非 ARC 领域,我们在这里假设 number 将在 numberWithInt: 内分配并返回 autoreleased。因此,当自动释放池下次排空时,它将被释放。那么让我们看看是否是这样(像往常一样,这是 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}
Run Code Online (Sandbox Code Playgroud)

嗯,是。这正是正在发生的事情。我们可以看到推送自动释放池的调用,然后调用 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}
Run Code Online (Sandbox Code Playgroud)

注意对 objc_retainAutoreleasedReturnValue 和 objc_release 的调用。发生的事情是 ARC 已经为我们确定它并不真正需要担心现有的自动释放池,因为它可以简单地告诉自动释放不发生(调用 objc_retainAutoreleasedReturnValue)然后稍后释放对象本身。这是可取的,因为这意味着不必发生自动释放逻辑。

请注意,仍然需要推送和弹出自动释放池,因为 ARC 无法知道在调用 numberWithInt: 和 NSLog 时发生了什么,以了解对象是否会被放入池中。如果它确实知道他们没有自动释放任何东西,那么它实际上可以摆脱推送和弹出。也许这种逻辑会在未来的版本中出现,尽管我不太确定它的语义是如何工作的。

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

void bar() {
    NSNumber *number;
    @autoreleasepool {
        number = [NSNumber numberWithInt:0];
        NSLog(@"number = %p", number);
    }
    NSLog(@"number = %p", number);
}
Run Code Online (Sandbox Code Playgroud)

您可能(正确地)认为这会导致问题,即使它看起来完全无害。这是一个问题,因为 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}
Run Code Online (Sandbox Code Playgroud)

显然没有像我们期望的那样调用保留、释放或自动释放,因为我们没有明确地做出任何调用,也没有使用 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}
Run Code Online (Sandbox Code Playgroud)

请为ARC鼓掌!请注意,它意识到我们在自动释放池块的范围之外使用 number,因此它保留了 numberWithInt: 的返回值,就像之前一样,但这次它把 release 放在 bar 函数的末尾而不是之前自动释放池被弹出。这将使我们避免一些我们可能认为是正确的代码崩溃,但实际上有一个微妙的内存管理错误。

  • 大家好。乔希 - 谢谢你指出,但不用担心亚历克斯 - 我不介意。也许只是一个链接就足够了,而不是复制和粘贴。但是你确实有属性所以这一切都很好。很高兴您首先发现这篇文章很有用。乔希 - 感谢您的关注。这让我意识到我需要在我的网站上获得许可才能明确我允许的内容。我将使其完全免费使用,但需要署名。 (3认同)