非ARC项目:NSMutableArray,NSString内存泄漏
我们的公司团队致力于现有的应用程序。该项目是非ARC(自动引用计数)。对于下面的代码释放对象存在疑问。非ARC项目:NSMutableArray,NSString内存泄漏
代码1:为什么执行此代码时没有崩溃?
NSMutableArray *arraytest=[[NSMutableArray alloc]init];
for(int i=0;i<100;i++)
{
NSString *str=[NSString stringWithFormat:@"string:%d",i];
[arraytest addObject:str];
}
NSLog(@"arraytest before:%@",arraytest);
[arraytest release];
NSLog(@"arraytest after:%@",arraytest);
类似这样的代码:与可变副本
代码2:修改后下面的代码崩溃的最后一道防线。
NSMutableArray *arraytest=[[NSMutableArray alloc]init];
for(int i=0;i<100;i++)
{
NSString *str=[NSString stringWithFormat:@"string:%d",i];
[arraytest addObject:str];
}
NSLog(@"arraytest before:%@",arraytest);
NSMutableArray *copyarray=[arraytest mutableCopy];
[arraytest release];
NSLog(@"copyarray:%@",copyarray);
NSLog(@"arraytest after:%@",arraytest);
为什么这行里有内存泄漏?
为什么会出现在此行中的内存泄漏?
什么是没有内存泄漏执行上面的代码正确的方法是什么?我们公司的人告诉autorelease不应该用于上面的代码。
考虑您的第一个例子:
NSMutableArray *arraytest = [[NSMutableArray alloc] init];
// `arraytest` populated ...
NSLog(@"arraytest before:%@", arraytest);
[arraytest release];
NSLog(@"arraytest after:%@", arraytest); // DANGER: referencing dangling pointer!!!
这最后NSLog
说法是极其危险的,因为有所谓的[arraytest release]
(减少了磁盘阵列的保留从+1
到0
计数),目标指向arraytest
会后就一直释放和arraytest
现在是dangling pointer到该释放的内存。在释放指针之后,不应该引用指针。有时候,它可能看起来像你可以使用它,但它不安全,你的应用程序现在可以意外崩溃。 (如果您使用的僵尸,不过,这将有安全警告过你你尝试错误使用这个悬摆指针的。)
考虑你的第二个例子:
NSMutableArray *arraytest = [[NSMutableArray alloc] init];
// `arraytest` populated ...
NSLog(@"arraytest before:%@",arraytest);
NSMutableArray *copyarray = [arraytest mutableCopy];
[arraytest release];
NSLog(@"copyarray:%@", copyarray);
NSLog(@"arraytest after:%@", arraytest); // DANGER: referencing dangling pointer!!!
在这个例子中,你还在你发布它之后,最后有arraytest
这个非常危险的NSLog
,并且你对那个悬挂指针的使用很容易崩溃。所以你想摆脱这一点。
但您现在已经引入了泄漏。当您发布arraytest
最初指出的对象时,您还没有发布copyarray
指向的对象,结果为mutableCopy
。因此,这个新的copyarray
实例将泄漏。因此,当您创建arraytest
时,最初分配的所有字符串现在将被泄漏的copyarray
引用,并且它们也会泄漏。
如果您在该例程的末尾添加了[copyarray release]
,则泄漏数组和泄漏字符串都将被解析。现在
,考虑你的第三个例子,仅在最后的仪器屏幕快照所示:
NSMutableArray *arraytest = [[NSMutableArray alloc] init];
for (int i=0; i<100; i++) {
NSString *str = [NSString stringWithFormat:@"string:%d", i];
[arraytest addObject:str];
[str release]; // DANGER: released `str` whose ownership was never transferred to you!!!
}
NSLog(@"arraytest before:%@",arraytest);
NSMutableArray *copyarray=[arraytest mutableCopy];
[arraytest release];
NSLog(@"copyarray:%@",copyarray);
NSLog(@"arraytest after:%@",arraytest); // DANGER: referencing dangling pointer!!!
在这个最后的例子中,我们使问题复杂化,通过overreleasing您刚才添加到字符串阵列。因此,您创建了一个autorelease对象(当autorelease池被排空时实际上保留计数为零),将其添加到数组(将其有效保留计数增加到+1
),并释放str
(将其保留计数减少为+0
排出池)。
该应用程序现在处于不稳定的情况,因为该数组现在引用了当自动释放池被耗尽时可能释放的对象,最后是一个悬挂指针数组。更糟糕的是,如果这个阵列本身被正确释放,所有这些字符串都会被过度发布。
而且,当然,如上面第二个例子中所讨论的,您仍然有阵列泄漏。但是如果你确实发布了这个copyarray
,那么所有这些字符串都会被过度发布。
也许不用在这一点上说,但要消除这种泄漏的方法是简单地释放copyarray
:
NSMutableArray *arraytest = [[NSMutableArray alloc] init];
for (int i=0; i<100; i++) {
NSString *str = [NSString stringWithFormat:@"string:%d", i];
[arraytest addObject:str];
}
NSLog(@"arraytest before:%@", arraytest);
NSMutableArray *copyarray = [arraytest mutableCopy];
[arraytest release];
NSLog(@"copyarray:%@", copyarray);
[copyarray release];
这是继Basic Memory Management Rules,即你是负责调用release
上那些凭借从alloc
,new
,copy
或mutableCopy
开始的方法接收到的对象。
一对夫妇收盘意见:
-
如果你打算使用手动引用计数,我建议你频繁使用Xcode的静态分析仪(转变 + 命令 + B或Xcode的“产品”菜单上的“分析”)。识别手动引用计数内存问题令人惊讶。仪器是有用的,但正如上面我们的第三个例子所显示的那样,您可以很容易地得出错误的结论(例如“哎呀,我需要释放所有这些字符串”)。静态分析器会为你指出一些这些问题。
底线,在进一步处理之前,始终确保您有来自静态分析仪的清洁健康证书。当分析仪能够准确告诉您问题是什么时,试图对仪器中可能出现的问题进行反向工程是毫无意义的。
我建议不要从一个特定的悬挂指针没有崩溃你的应用程序的事实得出任何结论,但另一个。这只是不可预测的。如果你打开僵尸(只是暂时的,为了开发/测试的目的,而不是为了生产应用程序),这会让你注意到任何尝试引用先前释放的对象。
-
我注意到您在测试中使用
NSString
。您应该知道NSString
有内部内存优化,可能会产生非标准的行为。在这些实验中,我会谨慎使用NSString
。不要误解我的意思:如果您按照所有的Basic Memory Management Rules,
NSString
将表现正常。但是,如果您试图在故意不遵循这些内存管理规则时检查导致什么样的错误/崩溃,请注意NSString
可能会产生误导。 不用说,使用ARC将极大地简化您的生活。有关更多信息,请参阅Transitioning to ARC Release Notes。