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

Rob*_*ert 2 memory-management objective-c ios automatic-ref-counting

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

这是示例代码:

ClassA.h

@interface ClassA : NSObject
    -(void)runSomething;
@end
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

ClassB.h

@interface ClassB : NSObject
@property int number;
    -(id)initWithNumber:(int)number;
    -(int)getNumber;
@end
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

ClassB *instance = [self makeClassBWithNumber:counter];

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

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

Mar*_*n R 5

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

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

Clang/ARC文档:

3.2.3未保留的返回值

返回可保留对象类型但不返回保留值的方法或函数必须确保对象在返回边界上仍然有效.

当从这样的函数或方法返回时,ARC在返回语句的评估点保留该值,然后保留所有本地范围,然后平衡保留,同时确保该值跨越调用边界. 在最坏的情况下,这可能涉及自动释放,但调用者不能假定该值实际上在自动释放池中.

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


Rob*_*ier 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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

这将导致池在每次迭代时耗尽.在任何情况下instance=nil都是不必要的.


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

  • @Robert:当自动释放池(在其中创建它们)结束时,释放自动释放的对象. - 有一个与主事件循环关联的自动释放池,因此如果您有一个有限循环并且程序控制返回到主事件循环,则释放对象. (2认同)