Objective-C 中的 release、autorelease 和数据类型

25

我对内存管理代码还不熟悉,但基本原理我已经了解了。

在使用XCode的泄漏检测工具时,我发现我只需要清理我的自定义对象,而无需处理动态创建的数组等数据类型,因此我认为这些数据类型是自动释放的——这很有道理,因为我只需要释放那些具有(retain)属性的数组,作为属性使用的数组都需要这样做。

然后我注意到一件奇怪的事情:我正在用下面这种方式初始化一个数组,但却出现了泄漏:

NSMutableArray *removals = [NSMutableArray new];

但不是类似的一个

NSMutableArray *removals = [NSMutableArray arrayWithCapacity:9];

现在,使用"new"设置第一个数组的原因是它可能有0-99个项目,而我知道另一个数组永远只会有9个项目。由于两个数组后来都会基于用户交互传递给同一个方法,如果我不在方法末尾释放第一个数组,就会发生泄漏,否则就会抛出异常!

我将第一个数组更改为

NSMutableArray *removals = [NSMutableArray arrayWithCapacity:99];

我没有泄漏并且不需要释放任何东西。有人能解释一下吗?

3个回答

65

正如在内存管理规则中所述,每当您使用+alloc+new-copy-mutableCopy创建一个对象时,您拥有该对象,并负责在某个时刻释放它。(事实上,+new只是[[MyClass alloc] init]的简写。) 如你所注意到的,通过[NSArray new]创建数组而没有释放它会导致内存泄漏。但是,如果正确处理此对象,则通常可以在某个时刻释放它。例如:

  • 如果使用数组的方法是从创建数组的方法内部调用的,那么您应该能够在使用完数组后将其释放。如果内部方法需要保持对数组的更持久引用,则该方法有责任向对象发送-retain和最终的-release。例如:

    - (void)myMethod {
        NSArray *removals = [NSArray new];
        // ...
        [someObject someOtherMethod:removals];
        [removals release];
    }
    
    如果你在对象的 -init 方法 中创建了该数组,则当对象被销毁时,-dealloc方法可以释放它。
    如果你需要创建该数组并从方法中返回它,那么你就发现了 autorelease 的存在理由。调用者不负责释放对象,因为它不是一个 +alloc+new-copy-mutableCopy 方法,但是你需要确保它最终被释放。在这种情况下,你需要在返回之前手动调用 -autorelease 方法。例如:
    - (NSArray *)myMethod {
        NSArray *removals = [NSArray new];
        // ...
        return [removals autorelease];
    }
    
    当您使用+arrayWithCapacity:创建数组时,您没有调用其中的“特殊”方法,因此不必释放结果。这可能是通过-autorelease实现的,就像上面的最后一个示例一样,但不一定如此。(顺便说一句,您还可以使用[NSMutableArray array]创建一个空的自动释放NSMutableArray;该方法位于NSArray中,因此在NSMutableArray文档中不会显示,但将其发送到NSMutableArray类时它将创建一个可变数组。)如果您要从方法中返回数组,则可以将其用作[[[NSMutableArray alloc] init] autorelease]的简写 - 但这只是一种快捷方式。然而,在许多情况下,您可以使用-init+new创建对象,并在适当的时候手动释放它。

7
这是事情在幕后的实现方式:
+(NSMutableArray*) new
{
    return [[NSMutableArray alloc] init];
}

and

+(NSMutableArray*) arrayWithCapacity:(NSNumber)capacity
{
    return [[NSMutableArray alloc] initWithCapacity:capacity] **autorelease**];
}

在第一种情况下,数组仅被分配,并且您需要负责释放它。相反,arrayWithCapacity已经为您自动释放了,即使您忘记释放也不会导致泄漏。


4

Cocoa使用特定的命名约定。以alloc、new或copy开头的任何内容都返回一个retainCount为1的对象,您需要释放它。函数返回的其他任何内容都具有平衡的retainCount(它可能被其他东西持有,也可能被保留而未释放)。

所以:

NSMutableArray *removals = [NSMutableArray new];

保留计数为1,并且:

NSMutableArray *removals = [NSMutableArray arrayWithCapacity:99];

或者

NSMutableArray *removals = [NSMutableArray array];

因为方法没有使用alloc、new或copy作为前缀,所以不要在这些方法中执行内存管理操作。这些详细说明都在内存管理文档中有所介绍。特别地:

如果您使用的是以“alloc”或“new”开头或包含“copy”的方法(例如alloc、newObject或mutableCopy)来创建对象,或者向其发送保留消息,则表示您拥有该对象的所有权。您负责使用释放或自动释放来释放您所拥有的对象。在任何其他时候,您接收到一个对象时,都不应该释放它。


1
所有这些方法都返回保留计数为1的对象。区别在于,对于某些方法,您拥有该对象,因此需要释放它,而对于其他方法,您不拥有该对象,无需释放它(但也不能指望它在当前调用链之后仍然存在)。 - Chuck
2
严格来说,它们不会,这是一种实现细节。在许多情况下,它们返回具有不同保留计数的内容。例如,[UIImage imageNamed:]可能会返回一个具有非常大的保留计数的内容,因为它可以重用缓存的图像。 - Louis Gerbarg
1
是的,保留计数本身是一项实现细节。苹果的文档也是如此说明的。而且在你上面列出的所有情况中,在当前版本的OS X上,这个实现细节的值为1。 - Chuck
1
当然,严格来说,没有人应该过于关注绝对保留计数,而是相对保留计数(包括任何待处理的自动释放池)。用这些术语来看,每个对象要么返回+1,要么返回0,具体取决于隐含的所有权。 - Louis Gerbarg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接