ARC和释放在方法中创build的对象
我偶然发现了一个我在其他地方找不到答案的问题。 当我调用一个返回指向一个稍后使用的对象的指针的方法,并且在结尾设置为零时,它仍然被分配在内存中(根据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
在视图控制器中创buildClassB,并调用runSomething方法。 此示例代码产生的创build对象(ClassB)不会从内存中释放。 如果我更改代码
ClassB *instance = [self makeClassBWithNumber:counter];
至
ClassB *instance = [[ClassB alloc] initWithNumber:counter];
创build的对象在每个循环中都被正确释放。 这种行为的原因是什么? 我在这里发现了一些旧的答案,在stackoverflow makeClassBWithNumber
应该返回结果调用autorelease return [result autorelease]
,但是这不能做,如果ARC启用。
不同之处在于+alloc
返回一个带有+1保留的对象,这个ARC将在其作用域末尾释放,并立即释放。 +make…
返回一个带有+1保留和匹配autorelease的对象。 自动释放池将在发送消息时发送release
消息。 既然你停留在循环“真实”,自动释放池永远不会消耗,你积累的内存。
解决的办法是给你的循环一个autorelease池:
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
是不必要的。
编辑:读马丁R的答案。 它给出了关于实现细节的更多细节,特别是为什么根据优化级别的不同,其行为可能会有所不同,以及被调用的方法与调用方法是否在同一个编译单元(.m文件)中。 这只是一个优化细节; 你仍然需要把这个@autoreleasepool
放在循环中以保证正确性。
makeClassBWithNumber
返回一个自动释放的对象,即使使用ARC。 (更确切地说,根据优化,它可以返回一个自动释放对象。)
与手动引用计数的区别在于,ARC编译器在需要时插入autorelease调用,而不是您。
从Clang / ARC文档:
3.2.3未保留的返回值
返回可保留对象types但不返回保留值的方法或函数必须确保对象在返回边界内仍然有效。
当从这样的function或方法返回时,ARC保留在返回语句评估点的值,然后离开所有本地范围,然后平衡保留,同时确保该值存在于呼叫边界。 在最坏的情况下,这可能涉及到autorelease ,但是调用者不能认为这个值实际上是在autorelease池中。
makeClassBWithNumber
不是alloc,copy,init,mutableCopy或new方法,因此返回一个未保留的返回值。
你的问题中的操作词是“老”。 旧的答案不再相关。
这是ARC的目的。
您不再需要担心任何内存pipe理。
如果ARC告诉你不要这样做…不要。
在这种情况下,你不需要autorelease。
正如其他人所说,你所看到的差异取决于一个方法是否返回并且对调用者拥有的对象或调用者不拥有的对象。
在前面的类中是alloc
, init
, new
, copy
和mutableCopy
类中的方法。 这些都会返callback用者拥有的对象,ARC将确保它被释放。
在后一类中,所有的方法都不在第一位! 这些返回一个不属于调用者的对象,ARC将确保这个对象在需要的时候被保留,如果被保留的话会被释放。 这个类别中的返回值可能在自动释放池中,这意味着它们将至less与池中一样长(如果它们已被ARC保留,则可能更长),并且标准池在最后清空每个运行循环周期。 这意味着在循环中产生大量的入口到自动释放池中,很多不再需要的对象可以在池中累积 – 这就是你所看到的。
在MRC下,解决scheme是在这样的循环中引入本地自动释放池,以避免不再需要的对象的积累。 但是在ARC下,这可能不是最好的select。
在ARC下,更好的解决scheme可能是遵循命名约定,在这种情况下,您希望使用new
模式 – new
是alloc
+ init
的标准模式。 因此,将您的makeClassBWithNumber
重命名为newClassBWithNumber
:
// *create* a new ClassB object - (ClassB *) newClassBWithNumber:(int)number { return [[ClassB alloc] initWithNumber:number]; }
这表明该方法返回一个调用者拥有的对象,这是一个“创build”方法,ARC将处理剩下的对象,而不再有对象累积。
(在ARC下添加一个newWithNumber
方法给ClassB
本身通常是一个好主意。)