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)
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!
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.
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.