【问题标题】:Non-ARC Project: NSMutableArray, NSString Memory Leak非 ARC 项目:NSMutableArray、NSString 内存泄漏
【发布时间】:2017-03-18 09:20:02
【问题描述】:

我们公司的团队正在处理现有的应用程序。该项目是非 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);

类似代码:带有可变副本

代码 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 不应该在代码上面使用。

【问题讨论】:

    标签: memory-leaks crash nsstring nsmutablearray


    【解决方案1】:

    考虑你的第一个例子:

    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 指向的对象将被释放并且@ 987654331@ 现在是该释放内存的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!!!
    

    在这个例子中,在你释放它之后,最后你仍然有那个非常危险的NSLogarraytest,并且你使用那个悬空指针很容易崩溃。所以你想摆脱它。

    但是您现在已经引入了泄漏。当你释放了arraytest 最初指向的对象时,你还没有释放copyarray 指向的对象,mutableCopy 的结果。因此,这个新的copyarray 实例将泄漏。因此,所有在您创建 arraytest 时最初分配的字符串现在都将被泄漏的 copyarray 引用,并且它们也会泄漏。

    如果您在此例程的末尾添加[copyarray release],则泄漏数组和泄漏字符串都将被解析。


    现在,考虑您的第三个示例,仅在最终的 Instruments 屏幕快照中显示:

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

    这跟在Basic Memory Management Rules 之后,即您负责对您拥有的那些对象调用release,因为这些对象是从以allocnewcopymutableCopy)。


    几个结束的观察:

    1. 如果你打算使用手动引用计数,我建议你经常使用 Xcode 的静态分析器 (shift+command+B ,或 Xcode 的“产品”菜单上的“分析”)。识别手动引用计数内存问题令人惊讶。 Instruments 很有用,但正如我们上面的第三个示例所示,您很容易得出错误的结论(例如,“哎呀,我需要释放所有这些字符串”)。静态分析器会为您指出其中的一些问题。

      最重要的是,在继续下一步之前,请始终确保您从静态分析仪获得了干净的健康状况。当分析器可以准确地告诉您问题是什么时,尝试对 Instruments 中可能出现的问题进行逆向工程是没有意义的。

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

    3. 我注意到您在测试中使用了NSString。您应该知道NSString 具有可能产生非标准行为的内部内存优化。在这类实验中使用NSString 时我会很谨慎。

      不要误会我的意思:如果您关注所有Basic Memory Management RulesNSString 将正常运行。但是,如果您试图检查故意不遵循这些内存管理规则会导致何种错误/崩溃,请注意NSString 可能会产生误导。

    4. 不用说,使用 ARC 将大大简化您的生活。请参阅Transitioning to ARC Release Notes 了解更多信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-22
      • 1970-01-01
      • 2015-06-20
      • 1970-01-01
      • 2012-04-03
      • 2011-10-31
      • 1970-01-01
      相关资源
      最近更新 更多