考虑你的第一个例子:
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!!!
在这个例子中,在你释放它之后,最后你仍然有那个非常危险的NSLog 或arraytest,并且你使用那个悬空指针很容易崩溃。所以你想摆脱它。
但是您现在已经引入了泄漏。当你释放了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,因为这些对象是从以alloc、new、copy 或mutableCopy)。
几个结束的观察:
-
如果你打算使用手动引用计数,我建议你经常使用 Xcode 的静态分析器 (shift+command+B ,或 Xcode 的“产品”菜单上的“分析”)。识别手动引用计数内存问题令人惊讶。 Instruments 很有用,但正如我们上面的第三个示例所示,您很容易得出错误的结论(例如,“哎呀,我需要释放所有这些字符串”)。静态分析器会为您指出其中的一些问题。
最重要的是,在继续下一步之前,请始终确保您从静态分析仪获得了干净的健康状况。当分析器可以准确地告诉您问题是什么时,尝试对 Instruments 中可能出现的问题进行逆向工程是没有意义的。
-
我建议不要从以下事实得出任何结论,即某个特定的悬空指针并没有使您的应用崩溃,但另一个却发生了。这只是不可预测的。如果您打开僵尸(只是暂时的,用于开发/测试目的,而不是用于生产应用程序),这将使您注意任何引用以前释放的对象的尝试。
-
我注意到您在测试中使用了NSString。您应该知道NSString 具有可能产生非标准行为的内部内存优化。在这类实验中使用NSString 时我会很谨慎。
不要误会我的意思:如果您关注所有Basic Memory Management Rules,NSString 将正常运行。但是,如果您试图检查故意不遵循这些内存管理规则会导致何种错误/崩溃,请注意NSString 可能会产生误导。
-
不用说,使用 ARC 将大大简化您的生活。请参阅Transitioning to ARC Release Notes 了解更多信息。