Why is object not dealloc'ed when using ARC + NSZombieEnabled
Solution 1:
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.
Solution 2:
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.
Solution 3:
Turns out it is an iOS bug. Apple has contacted me and indicated they've fixed this in iOS 6.