我将我的应用程序转换为ARC,并注意到在我的一个视图控制器中分配的对象在该视图控制器被解除分配时没有被释放。花了一段时间才弄清楚原因。我在调试时为我的项目启用了Zombie Objects,结果证明这是原因。请考虑以下应用程序逻辑:
1)用户调用RootViewController
中的操作,导致SecondaryViewController
被创建并通过presentModalViewController:animated
呈现。
2)SecondaryViewController
包含ActionsController
个NSObject
子类。
3)ActionsController
在初始化时通过NSNotificationCenter
观察通知,并在停止观察时停止观察。
4)用户驳回SecondaryViewController
以返回RootViewController
。
关闭“启用僵尸对象”,上面的工作正常,所有对象都被取消分配。即使ActionsController
已取消分配,也不会取消分配SecondaryViewController
上的“启用僵尸对象”。
这会导致我的应用程序出现问题b / c NSNotificationCenter
继续向ActionsController
发送通知,生成的处理程序会导致应用程序崩溃。
我在https://github.com/xjones/XJARCTestApp创建了一个简单的应用程序来说明这一点。打开/关闭启用僵尸对象的控制台日志以验证这一点。
问题(S)
编辑#1:根据凯文的建议,我已将此提交给Apple并在http://openradar.appspot.com/10537635向openradar提交。
编辑#2:澄清一个好的答案
首先,我是一位经验丰富的iOS开发人员,我完全理解ARC,僵尸对象等。如果我遗漏了某些东西,当然,我很欣赏任何照明。
其次,此特定崩溃的解决方法是在actionsController
取消分配时删除secondaryViewController
作为观察者。我还发现,如果我在actionsController = nil
被解除分配时明确设置secondaryViewController
,那么它将被解除分配。这两个都不是很好的解决方法b / c它们实际上要求你使用ARC但是代码好像你没有使用ARC(例如在dealloc中明确地使用nil iVars)。特定的解决方案也无法确定何时这会成为其他控制器中的问题,因此开发人员确定地知道何时/如何解决此问题。
一个好的答案将解释如何在使用ARC + NSZombieEnabled时确定性地知道你需要对某个对象做一些特殊的事情,这样它就可以解决这个具体的例子并且通常也适用于整个项目而不会留下潜力其他类似的问题。
完全有可能不存在好的答案,因为这可能是XCode中的错误。
谢谢大家!
答案 0 :(得分:9)
如果僵尸像我最初写的一样工作,打开僵尸会直接导致无数误报......
可能会在_objc_rootRelease
中进行一些混合调整,因此在启用僵尸的情况下仍应调用dealloc
的任何覆盖。僵尸不会发生的唯一事情是对object_dispose
的实际调用 - 至少在默认情况下不会。
有趣的是,如果你进行一些日志记录,你会发现即使启用了ARC,你的dealloc
实现也会调用它的超类的实现。
我实际上假设根本没有看到这个:因为ARC生成了这些时髦的.cxx_destruct
方法来处理类的任何__strong
ivars,所以我期待看到 this 方法调用dealloc
- 如果已实现。
显然,将NSZombieEnabled
设置为YES
会导致.cxx_destruct
根本不被调用 - 至少这是我编辑您的示例项目时发生的情况:
僵尸关闭导致回溯和两个deallocs,而僵尸产生没有回溯和只有一个dealloc。
如果您感兴趣,额外的日志记录包含在a fork of the sample project中 - 只需运行即可:僵尸开启/关闭有两种共享方案。
这不是一个错误,而是一个功能。
它与ARC无关。
NSZombieEnabled
基本上调动了dealloc
一个实现,反过来,isa-swizzles将对象的类型转换为_NSZombie
- 一个虚拟类,只要你发送任何消息就会爆炸它。这是预期的行为 - 如果我没有完全弄错 - 记录在案。
答案 1 :(得分:7)
这是Apple在Technical Q&A QA1758中承认的错误。
您可以通过将此代码编译到您的应用中来解决iOS 5和OS X 10.7的问题:
#import <objc/runtime.h>
@implementation NSObject (ARCZombie)
+ (void) load
{
const char *NSZombieEnabled = getenv("NSZombieEnabled");
if (NSZombieEnabled && tolower(NSZombieEnabled[0]) == 'y')
{
Method dealloc = class_getInstanceMethod(self, @selector(dealloc));
Method arczombie_dealloc = class_getInstanceMethod(self, @selector(arczombie_dealloc));
method_exchangeImplementations(dealloc, arczombie_dealloc);
}
}
- (void) arczombie_dealloc
{
Class aliveClass = object_getClass(self);
[self arczombie_dealloc];
Class zombieClass = object_getClass(self);
object_setClass(self, aliveClass);
objc_destructInstance(self);
object_setClass(self, zombieClass);
}
@end
您可以在我的博文Debugging with ARC and Zombies enabled中找到有关此变通办法的更多信息。
答案 2 :(得分:4)
原来这是一个iOS错误。 Apple已与我联系,并表示他们已在iOS 6中修复此问题。
答案 3 :(得分:0)
回答你需要从NSNotification中删除观察者的第二个问题 - 这将使它不会调用视图。
通常情况下,你会在dealloc中执行此操作但是有了僵尸问题可能它没有被调用。也许你可以把这个逻辑放在viewDidUnload中?
答案 4 :(得分:0)
因为你已经打开了NSZombieEnabled,所以这个对象不能调用dealloc,并把对象放到一个特殊的地方。你可以关闭NSZombieEnabled并再试一次。并仔细检查您的代码是否具有圆圈保留条件。