为啥有时会立即释放内存,而有时仅在自动释放池耗尽时才释放内存?

Posted

技术标签:

【中文标题】为啥有时会立即释放内存,而有时仅在自动释放池耗尽时才释放内存?【英文标题】:Why is memory sometimes deallocated immediately and other times only when autorelease pool is drained?为什么有时会立即释放内存,而有时仅在自动释放池耗尽时才释放内存? 【发布时间】:2014-04-08 22:39:45 【问题描述】:

我做了一个简单的实验,发现了一些奇怪的行为。这里有一些代码 - 启用 ARC 的 long 方法的一部分:

MKObject *obj = [[MKObject alloc]init];
NSMutableArray *temp = [[NSMutableArray alloc]init];
[temp addObject:obj];
obj = nil;
temp = nil;
//here deallocating is called on obj (MKObject)
//other stuff

但如果我将 NSMutableArray 更改为 NSArray 并进行文字初始化

NSArray *temp = @[obj];

在自动释放池关闭之前执行解除分配,而不是在将 nil 设置为所有引用之后执行。我错过了什么吗?

【问题讨论】:

【参考方案1】:

一些观察:

    在您的第一个示例中,MKObjectNSMutableArray 都不是自动释放对象,因此这些对象将立即释放,而不是等待自动释放池耗尽:

    MKObject *obj = [[MKObject alloc] init];
    NSMutableArray *temp = [[NSMutableArray alloc] init];
    [temp addObject:obj];
    obj = nil;
    temp = nil;
    

    在您的第二个示例中,NSArray 是一个自动释放对象,因此在自动释放池耗尽之前,NSArray(因此,MKObject)不会被释放。

    MKObject *obj = [[MKObject alloc] init];
    NSArray *temp = @[obj];
    obj = nil;
    temp = nil;
    

    要理解为什么数组字面量@[] 创建一个自动释放对象,应该注意它扩展为+[NSArray arrayWithObjects:count:]。每当您使用除 alloc 后跟 init(无论是 init 还是其中一种排列方式,例如 initWithObjects:)以外的任何方法实例化对象时,都会创建自动释放对象。

    如您所见,当应用创建自动释放对象时,该对象不会立即被释放,但会在自动释放池耗尽时释放。由于我们通常会快速返回到运行循环(此时池将被耗尽),因此在简单情况下选择自动释放对象或非自动释放对象几乎没有实际影响。但是,例如,如果应用程序有一个 for 循环,在该循环中它创建了许多自动释放对象而不返回到运行循环,那么它可能会出现问题(特别是如果 MKObject 很大或者您多次这样做)。例如:

    for (NSInteger i = 0; i < 100; i++) 
        MKObject *obj = [[MKObject alloc] init];
        NSArray *temp = @[obj];
    
        // Note, because they are local variables which are falling out of scope, I don't have to manually `nil` them.
    
    

    因为我们在这个例子中实例化了自动释放 NSArray 对象,所以上面的代码会将所有 100 个数组和对象保留在内存中,直到您返回到运行循环并且自动释放池有机会耗尽。这意味着应用程序的“高水位标记”(它在任何给定时间使用的最大内存量)将高于它可能需要的值。您可以通过以下任一方式解决此问题:

    使用非自动释放对象(例如使用alloc/init)而不是使用数组字面量:

    for (NSInteger i = 0; i < 100; i++) 
        MKObject *obj = [[MKObject alloc] init];
        NSArray *temp = [[NSArray alloc] initWithObjects:obj, nil];
    
    

    通过引入您自己的明确声明的@autoreleasepool

    for (NSInteger i = 0; i < 100; i++) 
        @autoreleasepool 
            MKObject *obj = [[MKObject alloc] init];
            NSArray *temp = @[obj];
        
    
    

    在最后一个示例中,自动释放池将在 @​​987654344@ 循环的每次迭代中耗尽,从而解决自动释放对象的任何挑战,否则它们的释放会延迟到循环结束。

    最后一个警告:通常,以allocinit(或init 方法的变体)开头的方法,不会生成自动释放对象,而所有其他方法,例如arrayWithObjects:count: 生成自动释放对象。一个值得注意的例外是 NSString 类,由于内部内存优化,它不符合此规则。因此,如果您有任何疑问,可以使用自己的手册@autoreleasepool,如果您反复实例化和释放对象,并且不确定对象是否为自动释放对象。而且,与往常一样,使用 Instruments 中的分配工具分析您的应用程序是观察应用程序高水位线的好方法。有关这些不同技术如何影响您应用的内存使用情况的说明,请参阅https://***.com/a/19842107/1271826。

【讨论】:

【参考方案2】:

阵列被自动释放池保留。如 Clang 的 Objective-C Literals 文档中所述,数组字面量语法扩展为对 +[NSArray arrayWithObjects:count:] 的调用,这会创建一个自动释放的数组。

【讨论】:

【参考方案3】:

我看到了几件事,虽然我对这个问题并不完全清楚,所以我不能说哪个适用:

将对象添加到 NSArray 或 NSMutableArray 会增加对象的保留计数。 在第一个实例中,您手动实例化 obj,使其保留计数为 1。 将其添加到 NSMutableArray 使其保留计数为 2。 在这种情况下,obj = nil 递减保留为 1; temp = nil 告诉数组处理释放其内容。那些 w/retain count 0 会立即被释放。

在第二个使用 @[] 字面量创建的实例中,引擎盖下的字面量语法使用方法 arrayWithObjects: count: 创建了一个自动释放的对象。当不再需要它时,它会进入自动释放池以进行最终释放。

这不是数组中的对象的问题,而是数组本身的创建方式问题。

编辑了我对以下 cmets 的原始回复 - 我对这个问题感到困惑。

【讨论】:

这似乎有点混乱。通常不需要将局部变量设置为 nil 以释放对象。当变量超出范围时,它们将被释放。对于使用显式方法调用创建的数组和通过字面量创建的数组(因为字面量只调用 arrayWithObjects:count:)同样如此,尽管在后一种情况下,数组可能仍由自动释放池保留。 Chuck 和 Rob,均正确 - 感谢您的检查。只是把几乎所有的东西都混在一起了,我没有……就这么多:) 上面的编辑回复。

以上是关于为啥有时会立即释放内存,而有时仅在自动释放池耗尽时才释放内存?的主要内容,如果未能解决你的问题,请参考以下文章

iphone:多次自动释放对象

为啥反复分配和释放内存会耗尽系统所有内存?

如何耗尽当前的自动释放池?

在帧写入循环中使用和不使用自动释放池的内存占用,为啥?

异步自动释放池

iOS之深入解析自动释放池autoreleasepool的底层原理