ARC中奇怪的对象生命周期:如何强制精确的对象生命周期?

时间:2014-01-29 17:06:43

标签: objective-c automatic-ref-counting

我在ARC中遇到了对象生命周期的奇怪行为。我把它缩小到这个例子:

//---------------------------------------------------------------------------------
@interface MyObject : NSObject
@end

@implementation MyObject
-(id)init
{
    self = [super init];
    if(!self) return nil;
    NSLog(@"    MyObject init %p", self);
    return self;
}
-(void)dealloc
{
    NSLog(@"    MyObject dealloc %p", self);
}
@end

//---------------------------------------------------------------------------------
@implementation TLAppDelegate

-(MyObject *)createMyObject:(NSString *)unusedArg
{
    return [MyObject new];
}

-(void)someOperation
{
    NSLog(@"  Entering someOperation");
    MyObject* x = [self createMyObject:@"some message"];
    NSLog(@"  Exiting someOperation; x should be deallocated right after this...");
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSLog(@"entering applicationDidFinishLaunching");
    [self someOperation];
    [self someOperation];
    NSLog(@"exiting applicationDidFinishLaunching");
}

@end

代码摘要:applicationDidFinishLaunching连续两次调用someOperationsomeOperation创建一个本地对象 我期望在someOperation返回时取消分配。

因此,这是我期望的输出

entering applicationDidFinishLaunching
  Entering someOperation
    MyObject init 0x600000012cd0
  Exiting someOperation; x should be deallocated right after this...
    MyObject dealloc 0x600000012cd0
  Entering someOperation
    MyObject init 0x600000012cb0
  Exiting someOperation; x should be deallocated right after this...
    MyObject dealloc 0x600000012cb0
exiting applicationDidFinishLaunching

但这是我实际得到的输出:

entering applicationDidFinishLaunching
  Entering someOperation
    MyObject init 0x600000012cd0    <-- this object is retained until the end of the output!
  Exiting someOperation; x should be deallocated right after this...
  Entering someOperation
    MyObject init 0x600000012cb0
  Exiting someOperation; x should be deallocated right after this...
    MyObject dealloc 0x600000012cb0
exiting applicationDidFinishLaunching
    MyObject dealloc 0x600000012cd0

为什么在applicationDidFinishLaunching返回之前第一个对象一直保留? 据我所知,MyObject实例不应该生活在someOperation之外。 这不是泄漏,因为它在下一个范围内被解除分配。

感觉就像编译器内联someOperation一样,将范围与调用者合并。但这也不是真的 (如果我错了,请纠正我),编译器不能内联Objective-C方法。

对于我的真实项目,这会在多个方面造成问题。首先在我们的日志记录类中,我们跟踪一些范围,但由于这种行为,以下日志:

Generating some list {
    Calculating item #1 {
    }
    Calculating item #2 {
    }
    Calculating item #3 {
    }
}

变成这个,这是没有意义的:

Generating some list {
    Calculating item #1 {
        Calculating item #2 {
            Calculating item #3 {
            }
        }
    }
}

此外,这意味着我们没有充分的理由继续留下太多的记忆。

有没有办法让这种行为更具可预测性?

请注意,如果我使用__attribute__((objc_precise_lifetime)),则此行为不会更改;它对观察到的行为没有任何改变。

1 个答案:

答案 0 :(得分:2)

MyObject不会在someOperation结束时发布,因为createMyObject返回的对象会保留(创建时)和自动释放(返回时) )。

因此,someOperation随后将该对象分配给x,并按照您的预期保留并释放它,但仍有来自createMyObject的自动释放引用,直到自动释放池被耗尽(通常在每个运行循环结束时发生)。

如果您不是从MyObject获取createMyObject,而是直接将其实例化为:

-(void)someOperation{
  NSLog(@"  Entering someOperation");
  MyObject* x = [[MyObject alloc] init];
  NSLog(@"  Exiting someOperation; x should be deallocated right after this...");
}

不会有自动释放的引用挂起,所有内容都应该按照您的预期立即解除分配。


更新

Martin R提出了关于ARC和命名约定的一个很好的观点。默认情况下,方法返回的对象由ARC保留/自动释放(如果没有保留,则立即为dealloc。如果没有自动释放,则会泄漏)。< / p>

根据Cocoa命名约定,有一些方法可以返回一个“保留”对象 - 即一个具有+1保留计数的非自动释放对象。对于名称以alloc…copy…init…mutableCopy…new…开头的特定方法,ARC将返回一个保留对象。其他所有东西都会返回一个自动释放的。