ARC和释放方法中创建的对象

时间:2013-09-10 19:38:56

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

我偶然发现了一个我无法在其他地方找到答案的问题。当我调用一个方法返回指向稍后使用的对象的指针并且在结尾设置为nil时,它仍然在内存中分配(根据Instruments)。我正在使用XCode 4.6.3和iOS 6.1。 ARC已开启。

以下是示例代码:

ClassA.h

@interface ClassA : NSObject
    -(void)runSomething;
@end

ClassA.m

#import "ClassA.h"
#import "ClassB.h"

@implementation ClassA

-(void)runSomething {
    int counter = 0;
    while (true) {
        ClassB *instance = [self makeClassBWithNumber:counter];
        NSLog(@"%d", [instance getNumber]);
        [NSThread sleepForTimeInterval:0.01];
        instance = nil;
        counter++;
    }
}

-(ClassB*) makeClassBWithNumber:(int)number {
    return [[ClassB alloc] initWithNumber:number];
}

@end

ClassB.h

@interface ClassB : NSObject
@property int number;
    -(id)initWithNumber:(int)number;
    -(int)getNumber;
@end

ClassB.m

#import "ClassB.h"

@implementation ClassB

-(id)initWithNumber:(int)number {
    self = [super init];
    if(self) {
        _number = number;
    }
    return self;
}

-(int)getNumber {
    return [self number];
}

@end

在视图控制器中创建ClassB,并调用runSomething方法。此示例代码生成从未从内存中释放创建的对象(ClassB)。如果我从

更改代码

ClassB *instance = [self makeClassBWithNumber:counter];

ClassB *instance = [[ClassB alloc] initWithNumber:counter];

创建的对象在每个循环周期中正确释放。这种行为的原因是什么?我在stackoverflow上找到了一些旧的答案makeClassBWithNumber应该返回调用autorelease return [result autorelease]的结果,但是如果启用了ARC则无法完成。

4 个答案:

答案 0 :(得分:5)

makeClassBWithNumber返回一个自动释放的对象,即使使用ARC也是如此。 (更准确地说,可以返回一个自动释放的对象,具体取决于优化。)

与手动引用计数的区别在于ARC编译器在需要时插入自动释放调用,而不是您。

来自Clang/ARC文档:

  

3.2.3未保留的返回值

     

返回可保留对象类型的方法或函数   不返回保留值必须确保对象仍然有效   越过回程边界。

     

从这样的函数或方法返回时,ARC保留该值   在评估退货声明时,然后全部离开   本地范围,然后平衡保留,同时确保   价值贯穿于呼叫边界。   在最坏的情况下,这可能涉及自动释放,但调用者不得假设值为   实际上是在自动释放池中。

makeClassBWithNumber不是alloc,copy,init,mutableCopy或new 方法,因此返回一个未保留的返回值。

答案 1 :(得分:5)

不同之处在于+alloc返回一个具有+1保留的对象,ARC将在其范围的末尾与释放进行平衡,因此立即释放。 +make…返回一个带有+1保留和匹配自动释放的对象。自动释放池将在消失时发送release消息。由于你保持循环“真实”,自动释放池永远不会消耗,你会积累内存。

解决方案是给你的循环一个自动释放池:

while (true) {
    @autoreleasepool { // <== Add an autorelease block here.
      ClassB *instance = [self makeClassBWithNumber:counter];
      //NSLog(@"%d", [instance getNumber]);
      NSLog(@"%d", [instance number]); // Fix naming; do not prefix accessors with `get`
      [NSThread sleepForTimeInterval:0.01];
      // instance = nil; // Does nothing in this loop.
      counter++;
    }
}

这会导致池在每次迭代时耗尽。无论如何,instance=nil是不必要的。


编辑:请阅读MartinR的回答。它提供了有关实现细节的更多详细信息,特别是为什么根据优化级别可能会有不同的行为,以及被调用方法与调用方法是否在同一个编译单元(.m文件)中。这只是一个优化细节;你仍然需要将这个@autoreleasepool放在循环中以保证正确性。

答案 2 :(得分:1)

你问题中的有效词是“老”。旧答案已不再适用。

这就是ARC的用途。

您不再需要担心任何内存管理。

如果ARC告诉你不要这样做......不要。

在这种情况下,您不需要自动释放。

答案 3 :(得分:1)

正如其他人所说,您看到的差异取决于方法是否返回调用者拥有的对象或调用者不拥有的对象。

前一类是allocinitnewcopy&amp; mutableCopy个类别。这些都返回调用者拥有的对象,ARC将确保它被释放。

在后一类中,所有方法都不在第一类!它们返回一个不属于调用者的对象,ARC将确保在需要时保留此对象,并在保留时释放它。此类别中的返回值可能位于自动释放池中,这意味着它们将至少与它们在池中一样长(如果它们被ARC保留,则可能更长)标准池在每个运行循环周期结束时清空。这意味着在自动释放池中生成大量条目的循环中,许多不再需要的对象可以在池中累积 - 这就是您所看到的。

在MRC下,解决方案是在这样的循环中引入本地自动释放池,以避免累积不再需要的对象。但是在ARC下,这可能是不是的最佳选择。

在ARC下,更好的解决方案可能是遵循命名约定,在这种情况下,您希望使用new模式 - newalloc + {{的标准模式1}}在一个方法中。因此,将init重命名为makeClassBWithNumber

newClassBWithNumber

这表示该方法返回一个调用者拥有的对象,它是一个“创建”方法,ARC将处理其余的,而不再有对象累积。

(向// *create* a new ClassB object - (ClassB *) newClassBWithNumber:(int)number { return [[ClassB alloc] initWithNumber:number]; } 本身添加newWithNumber方法通常是ARC的好主意。)