非ARC项目:NSMutableArray,NSString内存泄漏

时间:2016-11-04 13:50:15

标签: memory-leaks crash nsstring nsmutablearray

我们的公司团队致力于现有的应用程序。该项目是非ARC(自动参考计数)。关于在代码之后释放对象存在疑问。

代码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);

相似的代码:with mutable copy

代码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);

为什么这行存在内存泄漏?

enter image description here

为什么这行存在内存泄漏?

enter image description here

在没有内存泄漏的情况下执行上述代码的正确方法是什么?我们公司的人告诉autorelease不应该使用上面的代码。

1 个答案:

答案 0 :(得分:0)

考虑你的第一个例子:

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!!!

在这个示例中,在您释放它之后,最后仍然有NSLog arraytest非常危险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!!! ,则泄漏的数组和泄漏的字符串都将被解析。

现在,考虑你的第三个例子,仅在最终的仪器屏幕快照中显示:

+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];

release

这跟在Basic Memory Management Rules之后,即你负责通过从alloc开始的方法接收到你拥有的那些对象new,{{1} }},copymutableCopy)。

几个结束意见:

  1. 如果您要使用手动参考计数,我建议您经常使用Xcode的静态分析器( shift + 命令 + B ,或&#34;分析&#34;在Xcode&#34;产品&#34;菜单上)。它非常擅长识别手动引用计数内存问题。乐器很有用,但正如我们上面的第三个例子所示,你可以很容易地得出不正确的结论(例如,#34; gee,我需要释放所有这些字符串&#34;)。静态分析器会为你指出一些问题。

    最重要的是,在继续进行之前,请务必确保静电分析仪的健康状况良好。当分析仪准确地告诉你问题是什么时,试图逆向设计仪器中可能出现的问题没有任何意义。

  2. 我建议不要从一个特定的悬挂指针没有使你的应用程序崩溃的事实得出任何结论,但另一个确实如此。这是不可预测的。如果你打开僵尸(只是暂时的,用于开发/测试目的,而不是用于生产应用程序),这将引起你注意任何引用以前被释放的对象的尝试。

  3. 我注意到您在测试中使用NSString。您应该知道NSString具有内部内存优化,可以产生非标准行为。我在这些实验中使用NSString时要小心。

    不要误会我的意思:如果你遵循Basic Memory Management Rules的全部内容,NSString会表现得很好。但是,如果您在故意不遵守这些内存管理规则时尝试检查会导致哪种错误/崩溃,请注意NSString可能会产生误导。

  4. 毋庸置疑,使用ARC将大大简化您的生活。有关详细信息,请参阅Transitioning to ARC Release Notes