【问题标题】:Why is object not dealloc'ed when using ARC + NSZombieEnabled为什么在使用 ARC + NSZombieEnabled 时对象没有被释放
【发布时间】:2011-12-06 22:53:25
【问题描述】:

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

1) 用户在 RootViewController 中调用操作,导致 SecondaryViewController 创建并通过 presentModalViewController:animated 呈现。

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

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

4) 用户关闭SecondaryViewController 以返回RootViewController

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

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

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

问题

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

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

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

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

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

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

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

谢谢大家!

【问题讨论】:

  • 这听起来像一个错误。请file a bug report 并附上您的示例项目。
  • 感谢@Kevin,我已将错误报告提交给 Apple。
  • 另外,您应该将.DS_Store 添加到您的全局 git 忽略中。
  • 哎呀,我的 .gitconfig 搞砸了。感谢您关注@jim。

标签: iphone ios automatic-ref-counting nszombieenabled


【解决方案1】:

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

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

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

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

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

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

如果您有兴趣,额外的日志记录包含在 a fork of the sample project 中 — 只需运行即可:有两个用于僵尸开/关的共享方案。


原始(无意义)答案:

这不是一个错误,而是一个特性。

而且它与 ARC 无关。

NSZombieEnabled 基本上将dealloc 调换成一个实现,该实现反过来又将该对象的类型转换为_NSZombie——一个虚拟类,一旦你向它发送任何消息,它就会爆炸。这是预期的行为,并且 - 如果我没有完全弄错的话 - 记录在案。

【讨论】:

  • 如果只切换NSObject的dealloc函数,实现会更好,而所有子类的dealloc仍然会被调用。这样,dealloc 代码将运行(并且 ActionsController 将停止侦听通知),但该对象仍将是一个 _NSZombie 来捕获错误。
  • 我不认为这是正确的。使用 NSZombieEnabled,仍应调用原始的 dealloc。这确实发生在项目的非 ARC 版本中,它发生在 ARC 版本中的某些对象上,但不是全部。从而提示我的问题(和问题)。
  • @XJones 是的,我做了一个重要的“thinko”并得到纠正......道歉!
  • 没问题,@danyowdee。我很感激你为此付出的时间。 Apple 没有回复我的错误报告,所以我仍然对此一无所知。我对这是我的误解持开放态度(通常是这样的事情),但我没有想出任何解释为什么这是期望或预期的行为。
  • 现在,对我来说,它看起来像是一个运行时错误:通常,object_dispose 会调用.cxx_destruct(您可以使用我的项目分支轻松验证这一点)。反过来,此函数由-[NSObject dealloc] 调用,该函数在启用僵尸的情况下被替换,以便将对象的isa 与适当的_NSZombie 类交换。所以我会说这是一个疏忽:运行时应该调用objc_destructInstanceobject_cxxDestructFromClass 来代替-[NSObject dealloc],但不是。
【解决方案2】:

这是一个已被 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 中找到有关此解决方法的更多信息。

【讨论】:

  • 感谢您添加此内容。很有帮助。
  • 您是否介意在此答案中包含有关@gparker 对您的博客条目的回复的更新?我觉得这很重要,但我不想编辑你的答案,因为那会改变它的内容,而不是它的形式。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • secondaryViewController 被释放时删除actionsController 作为观察者确实解决了崩溃问题(我已经这样做了),但它不能解决对象未被释放的问题。请参阅我没有观察任何通知的示例项目。在这种情况下,我也可以将 actionsController 显式设置为 nil,但这也不是一个好的解决方案。这是针对此特定问题的补丁,但对应用程序其余部分中的对象没有信心。我将澄清问题中所需的解决方案类型。
    【解决方案5】:

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

    【讨论】:

    • 这是不正确的。对于僵尸对象调用 dealloc 方法。请参阅@danyowdee 的 anwser 和 cmets。
    猜你喜欢
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 2012-03-18
    • 1970-01-01
    • 2012-05-04
    • 1970-01-01
    • 1970-01-01
    • 2013-11-29
    相关资源
    最近更新 更多