我有这两个对象
//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;
}
在打印“完成”之前, child 似乎没有被释放。
但是如果我使用一个中间变量来保存子对象,释放似乎会很好。
@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");
}
我很好奇为什么会这样。
Best Answer-推荐答案 strong>
这里发生了很多事情,这是一个很好的探索示例。首先要意识到的是,dealloc 并没有在程序结束时发生。它发生在自动释放池的末尾(如 Julien 所述)。 ObjC 在程序终止时不运行 dealloc 。如果您将“完成”行移到自动释放池之外,您会看到这一点。要理解的第二件大事是,这与 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];
}
(稍微复杂一点,因为它也与 setter 同步,但这与本次讨论无关。)
所以现在我们有一个自动释放的 temp 卡在周围,它将在自动释放池的末尾被清理。如果您将 (nonatomic) 添加到您的属性定义中,您将看到该行为符合您的预期。
您的另一个示例从未对 [p2 child] 进行调用,因此它不会在其上添加额外的保留/自动释放,因此会更快地被释放。
这里的一个教训是,在大多数情况下,nonatomic 是首选。 atomic 被设为默认值有点令人惊讶,许多 Cocoa 开发人员几乎完全使用 nonatomic (大多数 Apple 示例代码也是如此)。额外的保留/自动释放的想法是它在多线程代码中提供了一些保护(没有它,您的局部变量可能会在您完成之前解除分配)。在实践中,通常有比 atomic 更好的方法来解决这个问题(并且 atomic 本身并不能为您提供实际的线程安全性)。也就是说,使用 atomic 属性没有问题,上面的代码没有错误;它只是需要多一点内存,时间长一点。
如果您对这些事情感到好奇,我总是建议您查看一下汇编输出。它可能有点难以阅读,但您通常可以了解很多关于编译器选择做什么的信息。在“助手” Pane 中,只需选择“装配”(从具有“对应物”的同一菜单中)。
关于ios - 为什么ARC在直接将对象分配给属性时无法正常工作,我们在Stack Overflow上找到一个类似的问题:
https://stackoverflow.com/questions/24705748/
|