为什么直接将对象分配给属性时ARC无法正常工作

时间:2014-07-11 20:10:32

标签: ios objective-c automatic-ref-counting

我有这两个对象

//Header file
#import <Foundation/Foundation.h>

@class Object2;

@interface Object1 : NSObject
@property Object2 *child;

@end

@interface Object2 : NSObject

@property (weak) Object1 *parent;

@end

// Implementation File
#import "MyClass.h"

@implementation Object1

-(void)dealloc{
    NSLog(@"deallocating parent");
}

@end

@implementation Object2

-(void)dealloc{
    NSLog(@"deallocating child");
}

@end

当我设置子和父关系而不为孩子引入新变量时,

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Object1 *p2 = [[Object1 alloc]init];
        p2.child = [[Object2 alloc]init];

        p2.child.parent = p2;

        NSLog(@"Setting p1 to nil");
        p2=nil;
        NSLog(@"Done");

    }
    return 0;
}

孩子似乎没有在“完成”之前解除分配。打印出来。

但是如果我使用中间变量来保存子对象,则释放似乎没有问题。

@autoreleasepool {
    Object1 *p1 = [[Object1 alloc]init];
    Object2 *c1 = [[Object2 alloc]init];
    p1.child = c1;

    c1.parent = p1;
    c1 = nil;

    NSLog(@"Setting p1 to nil");
    p1=nil;
    NSLog(@"Done");


}

我很好奇为什么会这样。

2 个答案:

答案 0 :(得分:4)

这里有很多事情,这是一个很好的例子。首先要意识到的是,dealloc并不是在程序结束时发生的。它发生在自动释放池的末尾(正如Julien指出的那样)。 ObjC在程序终止时没有dealloc运行。如果你移动了&#34;完成&#34;你会看到这个。自动释放池外的一行。要理解的第二件大事是,这与ARC无关。行为与MRC相同。

那么为什么与中间人的区别呢?那么,你需要考虑这条线的含义:

p2.child.parent = p2;

真的:

[[p2 child] setParent:p2];

这相当于:

id temp = [p2 child];
[temp setParent:p2];

[p2 child]的调用永远不会发生在你的另一个例子中(它调用[p2 setChild:],这完全不同)。

您已使用child的默认属性设置。默认设置包括atomic。这意味着我们的getter看起来像:

- (Object2 *)child {
  return [[_child retain] autorelease];
}

(它有点复杂,因为它也与设定者同步,但这对这个讨论并不重要。)

所以现在我们有一个自动释放的temp,它将在自动释放池的末尾清理。如果您在属性定义中添加了(nonatomic),则会发现该行为符合您的预期。

你的另一个例子从未对[p2 child]进行调用,因此它不会对其进行额外的保留/自动释放,因此会更快地解除分配。

这里的一个教训是,在大多数情况下nonatomic是首选。有点令人惊讶的是,atomic是默认的,许多Cocoa开发人员几乎只使用nonatomic(大多数Apple示例代码也是如此)。额外保留/自动释放的想法是它在多线程代码中提供了一些保护(没有它,你的局部变量可能在你完成之前解除分配)。在实践中,通常有更好的方法来解决这个问题,而不是atomic(并且atomic并不能为您提供实际的线程安全性)。也就是说,使用atomic属性并不是问题,上面的代码没有错误;它只需要更长时间的内存。

如果您对此类事情感到好奇,我总是建议您查看汇编输出。它可能有点难以阅读,但您通常可以了解编译器选择做什么。在“助手”窗格中,只需选择&#34; Assembly&#34; (来自具有&#34;对应部分&#34;的相同菜单。)

答案 1 :(得分:0)

问题在于:

p2.child = [[Object2 alloc]init];

创建&#34;保留/自动释放&#34;对象并调用一个将保留该对象的setter,因此该对象将在自动释放池的闭包处解除分配。编译器不够智能,无法优化自动释放。

不知何故,ARC将您的代码编译为MRR的等价物:

p2.child = [[[Object2 alloc]init] autorelease];

当你写:

Object2 *c1 = [[Object2 alloc]init];

编译器非常智能,可以在调用c1 = nil时优化自动释放并使其成为简单版本。

ARC将您的代码编译为MRR的等效代码:

Object2 *c1 = [[Object2 alloc]init]; ... [c1 release]; c1 = nil;

正如旁注所示,对象总是被正确地解除分配,它最终在@autoreleasepool被解除分配:

2014-07-11 13:18:03.233 TestWeak[48241:303] Setting p1 to nil 2014-07-11 13:18:03.235 TestWeak[48241:303] deallocating parent 2014-07-11 13:18:03.235 TestWeak[48241:303] Done 2014-07-11 13:18:03.236 TestWeak[48241:303] deallocating child