35

我将我的应用程序转换为 ARC,并注意到在我的一个视图控制器中分配的对象在该视图控制器被解除分配时没有被解除分配。花了一段时间才弄清楚为什么。我在调试时为我的项目启用了“启用僵尸对象”,结果证明这是原因。考虑以下应用程序逻辑:

1) 用户调用RootViewController导致 aSecondaryViewController被创建和呈现的动作presentModalViewController:animated

2)SecondaryViewController包含ActionsController一个NSObject子类。

3)在初始化时ActionsController观察通知,NSNotificationCenter并在解除分配时停止观察。

4) 用户解散SecondaryViewController返回RootViewController

关闭启用僵尸对象后,上述工作正常,所有对象都被释放。启用 Zombie Objects 后,ActionsController即使SecondaryViewController已解除分配,也不会解除分配。

这导致了我的应用程序 b/cNSNotificationCenter继续向其发送通知的问题ActionsController,由此产生的处理程序导致应用程序崩溃。

我在https://github.com/xjones/XJARCTestApp创建了一个简单的应用程序来说明这一点。查看启用/关闭启用僵尸对象的控制台日志以验证这一点。

问题)

  1. 这是启用僵尸对象的正确行为吗?
  2. 我应该如何实现这种类型的逻辑来消除问题。我想继续使用启用僵尸对象。

编辑#1:根据 Kevin 的建议,我已在http://openradar.appspot.com/10537635将其提交给 Apple 和 openradar 。

编辑#2:澄清一个好的答案

首先,我是一位经验丰富的 iOS 开发人员,我完全了解 ARC、僵尸对象等。如果我遗漏了什么,当然,我很感激任何启发。

其次,对于这种特定崩溃的解决方法确实是在释放actionsController时作为观察者移除。secondaryViewController我还发现,如果我明确设置actionsController = nil何时secondaryViewController解除分配,它将被解除分配。这两种方法都不是很好的解决方法 b/c 它们实际上要求您使用 ARC,但代码就像您没有使用 ARC 一样(例如,在 dealloc 中显式地 nil iVars)。特定的解决方案也无助于确定这在其他控制器中何时会成为问题,因此开发人员可以确定性地知道何时/如何解决此问题。

一个好的答案将解释如何确定性地知道在使用 ARC + NSZombieEnabled 时您需要对一个对象做一些特殊的事情,这样它就可以解决这个特定的例子,并且通常也适用于整个项目,而没有其他类似的可能性问题。

完全有可能不存在一个好的答案,因为这可能是 XCode 中的一个错误。

谢谢大家!

4

5 回答 5

9

原来,我写了一些严肃的废话

如果僵尸像我最初写的那样工作,打开僵尸会直接导致无数误报......

有一些 isa-swizzling 正在进行,可能在 中,所以仍然应该在启用僵尸_objc_rootRelease的情况下调用任何覆盖。dealloc僵尸不会发生的唯一事情是实际调用object_dispose- 至少默认情况下不会。

有趣的是,如果你做一些记录,你实际上会看到即使启用了 ARC,你的实现dealloc也会调用它的超类的实现。

我实际上假设根本看不到这一点:由于 ARC 生成了这些时髦.cxx_destruct的方法来处理__strong类的任何 ivars,我期待看到这个方法调用dealloc——如果它被实现的话。

显然,设置NSZombieEnabled为根本不调用的YES原因.cxx_destruct- 至少在我编辑您的示例项目时发生了这种情况:
关闭僵尸会导致回溯和两个释放,而僵尸打开不会产生回溯并且只有一个释放。

如果您有兴趣,额外的日志记录包含在示例项目的一个分支中——只需运行即可:有两个用于僵尸开/关的共享方案。


原始(荒谬)答案:

这不是一个错误,而是一个功能。

它与ARC无关。

NSZombieEnabled基本上是dealloc为一个实现进行调配,而该实现又将该对象的类型再调配到_NSZombie一个虚拟类,一旦你向它发送任何消息,它就会崩溃。这是预期的行为,并且 - 如果我没有完全弄错的话 - 已记录在案。

于 2011-12-26T21:13:34.550 回答
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中找到有关此解决方法的更多信息。

于 2012-08-18T13:58:45.873 回答
4

原来这是一个iOS错误。Apple 已联系我并表示他们已在 iOS 6 中修复了此问题。

于 2012-07-13T18:33:04.927 回答
0

要回答第二个问题,您需要从 NSNotification 中删除观察者 - 这将阻止它调用视图。

通常,您会在 dealloc 中执行此操作,但对于僵尸问题,它可能不会被调用。也许你可以把这个逻辑放在 viewDidUnload 中?

于 2011-12-07T12:45:33.567 回答
0

因为你打开了NSZombieEnabled,这让对象不会调用dealloc,而把对象放到一个特殊的地方。您可以关闭 NSZombieEnabled 并重试。并仔细检查您的代码是否具有循环保留条件。

于 2012-05-18T18:42:27.027 回答