我偶然发现了一个我无法在其他地方找到答案的问题。当我调用一个方法返回指向稍后使用的对象的指针并且在结尾设置为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则无法完成。
答案 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
是不必要的。
@autoreleasepool
放在循环中以保证正确性。
答案 2 :(得分:1)
你问题中的有效词是“老”。旧答案已不再适用。
这就是ARC的用途。
您不再需要担心任何内存管理。
如果ARC告诉你不要这样做......不要。
在这种情况下,您不需要自动释放。
答案 3 :(得分:1)
正如其他人所说,您看到的差异取决于方法是否返回调用者拥有的对象或调用者不拥有的对象。
前一类是alloc
,init
,new
,copy
&amp; mutableCopy
个类别。这些都返回调用者拥有的对象,ARC将确保它被释放。
在后一类中,所有方法都不在第一类!它们返回一个不属于调用者的对象,ARC将确保在需要时保留此对象,并在保留时释放它。此类别中的返回值可能位于自动释放池中,这意味着它们将至少与它们在池中一样长(如果它们被ARC保留,则可能更长)标准池在每个运行循环周期结束时清空。这意味着在自动释放池中生成大量条目的循环中,许多不再需要的对象可以在池中累积 - 这就是您所看到的。
在MRC下,解决方案是在这样的循环中引入本地自动释放池,以避免累积不再需要的对象。但是在ARC下,这可能是不是的最佳选择。
在ARC下,更好的解决方案可能是遵循命名约定,在这种情况下,您希望使用new
模式 - new
是alloc
+ {{的标准模式1}}在一个方法中。因此,将init
重命名为makeClassBWithNumber
:
newClassBWithNumber
这表示该方法返回一个调用者拥有的对象,它是一个“创建”方法,ARC将处理其余的,而不再有对象累积。
(向// *create* a new ClassB object
- (ClassB *) newClassBWithNumber:(int)number
{
return [[ClassB alloc] initWithNumber:number];
}
本身添加newWithNumber
方法通常是ARC的好主意。)