Why is object not dealloc'ed when using ARC + NSZombieEnabled
Asked Answered
I

5

35

I converted my app to ARC and noticed that an object alloc'ed in one of my view controllers was not being dealloc'ed when that view controller was dealloc'ed. It took a while to figure out why. I have Enable Zombie Objects on for my project while debugging and this turned out to be the cause. Consider the following app logic:

1) Users invokes action in RootViewController that causes a SecondaryViewController to be created and presented via presentModalViewController:animated.

2) SecondaryViewController contains an ActionsController that is an NSObject subclass.

3) ActionsController observes a notification via NSNotificationCenter when it is initialized and stops observing when it is dealloc'ed.

4) User dismisses SecondaryViewController to return to RootViewController.

With Enable Zombie Objects turned off, the above works fine, all objects are deallocated. With Enable Zombie Objects on ActionsController is not deallocated even though SecondaryViewController is deallocated.

This caused problems in my app b/c NSNotificationCenter continues to send notifications to ActionsController and the resulting handlers cause the app to crash.

I created a simple app illustrating this at https://github.com/xjones/XJARCTestApp. Look at the console log with Enable Zombie Objects on/off to verify this.

QUESTION(S)

  1. Is this correct behavior of Enable Zombie Objects?
  2. How should I implement this type of logic to eliminate the issue. I would like to continue using Enable Zombie Objects.

EDIT #1: per Kevin's suggestion I've submitted this to Apple and openradar at http://openradar.appspot.com/10537635.

EDIT #2: clarification on a good answer

First, I'm an experienced iOS developer and I fully understand ARC, zombie objects, etc. If I'm missing something, of course, I appreciate any illumination.

Second, it is true that a workaround for this specific crash is to remove actionsController as an observer when secondaryViewController is deallocated. I have also found that if I explicitly set actionsController = nil when secondaryViewController is dealloc'ed it will be dealloc'ed. Both of these are not great workaround b/c they effectively require you to use ARC but code as if you are not using ARC (e.g. nil iVars explicitly in dealloc). A specific solution also doesn't help identify when this would be an issue in other controllers so developers know deterministically when/how to workaround this issue.

A good answer would explain how to deterministically know that you need to do something special wrt an object when using ARC + NSZombieEnabled so it would solve this specific example and also apply generally to a project as a whole w/o leaving the potential for other similar problems.

It is entirely possible that a good answer doesn't exist as this may be a bug in XCode.

thanks all!

Indictable answered 6/12, 2011 at 22:53 Comment(4)
This sounds like a bug. Please file a bug report and attach your sample project.Iciness
Thanks @Kevin, I submitted the bug report to Apple.Indictable
On a separate note, you should probably add .DS_Store to your global git ignores.Mcquade
oops, my .gitconfig was screwed up. thanks for noticing @jim.Indictable
I
4

Turns out it is an iOS bug. Apple has contacted me and indicated they've fixed this in iOS 6.

Indictable answered 13/7, 2012 at 18:33 Comment(0)
J
9

Turns out, I've written some serious nonsense

If zombies worked like I originally wrote, turning on zombies would directly lead to innumerable false positives...

There is some isa-swizzling going on, probably in _objc_rootRelease, so any override of dealloc should still be called with zombies enabled. The only thing that won't happen with zombies is the actual call to object_dispose — at least not by default.

What's funny is that, if you do a little logging, you will actually see that even with ARC enabled, your implementation of dealloc will call through to it's superclass's implementation.

I was actually assuming to not see this at all: since ARC generates these funky .cxx_destruct methods to dispose of any __strong ivars of a class, I was expecting to see this method call dealloc — if it's implemented.

Apparently, setting NSZombieEnabled to YES causes .cxx_destruct to not be called at all — at least that's what happened when I've edited your sample project:
zombies off leads to backtrace and both deallocs, while zombies on yields no backtrace and only one dealloc.

If you're interested, the additional logging is contained in a fork of the sample project — works by just running: there are two shared schemes for zombies on/off.


Original (nonsensical) answer:

This is not a bug, but a feature.

And it has nothing to do with ARC.

NSZombieEnabled basically swizzles dealloc for an implementation which, in turn, isa-swizzles that object's type to _NSZombie — a dummy class that blows up, as soon as you send any message to it. This is expected behavior and — if I'm not entirely mistaken — documented.

Jonellejones answered 26/12, 2011 at 21:13 Comment(7)
The implementation would be better if only the NSObject dealloc function was switched, while dealloc on all subclass would still be called. That way the dealloc code would run (and ActionsController would stop listening for notifications), but the object would still be an _NSZombie to catch bugs.Kathrynekathy
I don't think this is correct. With NSZombieEnabled, the original dealloc should still be invoked. This does happen in the non-ARC version of the project and it happens for some objects in the ARC version but not all. Thus prompting my issue (and question).Indictable
@Indictable Yeah, I've made a major "thinko" and stand corrected... Apologies!Jonellejones
No problem at all, @danyowdee. I appreciate the time you put into this. Apple hasn't responded to my bug report so I'm still in the dark on this. I'm open to this being a misunderstanding on my part (things like this usually are) but I haven't come up with any explanation why this is the desired or intended behavior.Indictable
By now, it looks like a runtime-bug to me: normally, object_dispose calls through to .cxx_destruct (you can easily verify this with my fork of your project). This function, in turn, gets called by -[NSObject dealloc], which — with zombies enabled – get's replaced in order to exchange the object's isa with the appropriate _NSZombie class. So I'd say it's an oversight: the runtime should be calling objc_destructInstance or object_cxxDestructFromClass in the replacement for -[NSObject dealloc], but isn't.Jonellejones
PS: It's a great question and I've learned quite a bit trying to answer it ;-) Oooh and I'm duping your bug!Jonellejones
I just stumbled into this as well and have another question open for it - #8989718 . I've voted to close it as a dupe of this by the way. But in my answer to my own question, you'll see a much more cut down "app" which shows the bug. I've filed as a radar as well. Hopefully they'll fix it soon! I lost half a day on this!Obolus
L
7

This is a bug that has been acknowledged by Apple in Technical Q&A QA1758.

You can workaround on iOS 5 and OS X 10.7 by compiling this code into your app:

#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

You will find more information about this workaround in my blog post Debugging with ARC and Zombies enabled.

Lardaceous answered 18/8, 2012 at 13:58 Comment(1)
Would you mind including the update regarding @gparker’s response to your blog entry in this answer? I feel this is important but don’t want to edit your answer, as that would be changing its content, not its form.Jonellejones
I
4

Turns out it is an iOS bug. Apple has contacted me and indicated they've fixed this in iOS 6.

Indictable answered 13/7, 2012 at 18:33 Comment(0)
T
0

to answer the second question you would need to remove the observer from NSNotification - that will keep it from calling the view.

Normally, you would do this in the dealloc but with that zombie issue maybe it's not getting called. Maybe you could put that logic in viewDidUnload?

Tactual answered 7/12, 2011 at 12:45 Comment(1)
Removing actionsController as an observer when secondaryViewController is dealloc'ed does solve the crash (I already did that) but it does not solve the problem of the object not being deallocated. See my sample project which does not have observe any notifications. I can also explicitly set actionsController to nil in this case but that is also not a good solution. It is a patch for this particular issue but gives no confidence regarding objects in the rest of the app. I'll clarify the type of solution required in the question.Indictable
G
0

Because you have open NSZombieEnabled, this let the object not call dealloc, and put the object to a special place. you can close NSZombieEnabled and have try again. And double check if your code have circle retain condition.

Gingili answered 18/5, 2012 at 18:42 Comment(1)
this is not correct. the dealloc method is called for zombie objects. see @danyowdee's anwser and comments.Indictable

© 2022 - 2024 — McMap. All rights reserved.