What is NSZombie?
It's a memory debugging aid. Specifically, when you set NSZombieEnabled
then whenever an object reaches retain count 0, rather than being deallocated it morphs itself into an NSZombie
instance. Whenever such a zombie receives a message, it logs a warning rather than crashing or behaving in an unpredictable way. As such, you can debug subtle over-release/autorelease problems without advanced tools or painstaking needle in haystack searches.
The name is a fairly obvious play on the fact that objects are normally considered "dead" when they reach retain count 0. With this setting, they continue to exist in a strange half-life - neither living, nor quite dead. Much like real zombies, except they eat rather fewer brains.
Why does object become NSZombie only when inherit from NSObject?
tl;dr: because NSZombies
are implemented to only affect NSObject
and its subclasses. (This doesn't have to do with Swift, either: Obj-C objects which aren't subclasses of NSObject
also won't become zombies.)
Upon initialization (__CFInitialize
, called when the framework is loaded), the CoreFoundation framework sets up a lot low-level Foundation and CoreFoundation behaviors; among other things, it looks for the NSZombieEnabled
environment variable, and if present, enables zombies by calling the __CFZombifyNSObject
function:
; Disassembly from Hopper on /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation on macOS Catalina
0000000000001cc0 lea rdi, qword [aNszombieenable] ; argument #1 for method ___CFgetenv, "NSZombieEnabled"
0000000000001cc7 call ___CFgetenv ; ___CFgetenv
0000000000001ccc test rax, rax
0000000000001ccf je loc_1cee
0000000000001cd1 mov al, byte [rax] ; DATA XREF=sub_1bb8ec
0000000000001cd3 or al, 0x20
0000000000001cd5 cmp al, 0x79
0000000000001cd7 jne loc_1cee
0000000000001cd9 cmp byte [___CFZombieEnabled], 0x0 ; ___CFZombieEnabled
0000000000001ce0 jne loc_1cee
0000000000001ce2 mov byte [___CFZombieEnabled], 0xff ; ___CFZombieEnabled
0000000000001ce9 call ___CFZombifyNSObject ; ___CFZombifyNSObject
When zombies are enabled, __CFZombifyNSObject
replaces the implementation of -[NSObject dealloc]
with a different implementation (__dealloc_zombie
):
// Hopper-generated pseudo-code:
void ___CFZombifyNSObject() {
rax = objc_lookUpClass("NSObject");
method_exchangeImplementations(class_getInstanceMethod(rax, @selector(dealloc)), class_getInstanceMethod(rax, @selector(__dealloc_zombie)));
return;
}
This means that all subclasses of NSObject
and their descendants, upon deallocation, will call through to __dealloc_zombie
. So what does __dealloc_zombie
do?
// Hopper-generated pseudo-code:
/* @class NSObject */
-(void)__dealloc_zombie {
rbx = self;
if ((rbx & 0x1) != 0x0) goto loc_175ed5;
loc_175e3f:
if (*(int8_t *)___CFZombieEnabled == 0x0) goto loc_175eee;
loc_175e4c:
rax = object_getClass(rbx);
var_20 = 0x0;
rax = asprintf(&var_20, "_NSZombie_%s", class_getName(rax));
rax = objc_lookUpClass(var_20);
r14 = rax;
if (rax == 0x0) {
r14 = objc_duplicateClass(objc_lookUpClass("_NSZombie_"), var_20, 0x0);
}
free(var_20);
objc_destructInstance(rbx);
object_setClass(rbx, r14);
if (*(int8_t *)___CFDeallocateZombies != 0x0) {
free(rbx);
}
goto loc_175ed5;
loc_175ed5:
if (**___stack_chk_guard != **___stack_chk_guard) {
__stack_chk_fail();
}
return;
loc_175eee:
if (**___stack_chk_guard == **___stack_chk_guard) {
_objc_rootDealloc(rbx);
}
else {
__stack_chk_fail();
}
return;
}
In more human-readable terms, it:
- Looks up
[self class]
- If there isn't already a class named
_NSZombie_
, it creates one by duplicating the_NSZombie_
class and gives the duplicate a new name (this creates a copy of the class with all of its method implementations, or lack thereof) - It tears down
self
, and replaces its class with the new class, so that if you message it in the future, you dispatch to_NSZombie_
_NSZombie_
is a class which implements no methods, so sending it any message (method call) ends up falling into a code path in message forwarding which prints out the "message sent to deallocated instance" message.
Effectively, this method of implementing zombies hinges on inheritance from NSObject
(because all NSObject
subclasses should call [super dealloc]
on deallocation, eventually reaching [NSObject dealloc]
); things which don't inherit from NSObject
don't inherit this implementation. (You can also actually see this by implementing an NSObject
subclass which doesn't call [super dealloc]
in its -dealloc
implementation — the object won't get zombified on release.)
Do NSZombies
have to be implemented this way? No, it's certainly possible to imagine other schemes which would allow pure Swift objects to participate (Swift runtime initialization could also look up the NSZombieEnabled
environment variable and do something similar), but there's somewhat less of a benefit to putting in the effort. As Rob mentions in his answer, this works largely because we're able to swizzle out the class of the deallocated instance (this is actually possible with the Swift runtime, but not exposed externally), but crucially, even if we did this, it wouldn't help cases of static method dispatch, which is possible on object types in Swift (e.g. for final
classes). [Alexander alludes to this in his comment.]
So, largely, it's really easy to implement this way for Obj-C, and there are somewhat limited benefits for taking the time to do this for pure Swift classes too.
How to add NSDebug.h and use NSZombie in iPhone SDK
You don't have to include NSDebug.h or import any special frameworks to use NSZombies. Basically, turn 'em on in your environment variables, and then, if you attempt to message a dealloc'd object, THEN you'll see something in your console, along the lines of:
2009-02-10 21:17:08.546 MyApp[16926:20b] *** -[CFString _cfTypeID]: message sent to deallocated instance 0x4babc0
Objective C: How to tell if an object is NSZombie now
A pointer to an object is never set to nil when the object is released. The pointer continues to point to the same memory location that it always did. That doesn't mean that whatever is now at that location is a valid object, however. For that reason, you should never use a pointer after you've released the object it points to. If you're going to continue to use that pointer, you should change its value so that it's either nil or points to some other (valid) object.
NSZombieEnabled is just a debugging tool that helps you find places in your code where you're accessing invalid objects. If you've found a place where you're doing that, you've found a bug in your code and you need to fix it, not tolerate it.
Change your code so that you properly set your udid pointer to nil once you've released it.
How to enable NSZombie in Xcode?
Environment variables are now part of the "scheme".
To edit the scheme and turn on zombies:
In the "Product" menu, select "Edit Scheme".
Go to the "Run Foo.app" stage in the left panel, and the "Arguments" tab on the right.
Add
NSZombieEnabled
to the "Environment Variables" section and set the value toYES
, as you could in Xcode 3.
In Xcode 4.1 and above, there's also a checkbox on the "Diagnostics" tab of the "Run" stage to "Enable Zombie Objects".
With Xcode 6.4:
Found an NSZombie in my app... now what?
I ran into a very similar problem myself recently and it took me a while to figure out what I was doing wrong. An extra release is being issued here -- because your pointer returnValue
is __strong
(the default), ARC believes that the object is owned via that pointer, but that's not the case.
-[NSInvocation getReturnValue:]
doesn't take ownership of it, and the "assignment" via the pointer's address bypasses the objc_storeStrong()
that ARC would normally use.
The solution is simple: mark the pointer as __unsafe_unretained
. This is the truth; the object is not retained via this pointer (as it would if it were __strong
), and ARC must not release it here.
Related Topics
How to Load Local HTML File into Uiwebview
Detect If the App Was Launched/Opened from a Push Notification
Move a View Up Only When the Keyboard Covers an Input Field
Sharing Data in Between Apps in Ios
Setting Custom Uitableviewcells Height
Core Data and iOS 7: Different Behavior of Persistent Store
Uibutton: How to Center an Image and a Text Using Imageedgeinsets and Titleedgeinsets
How to Import Own Classes from Your Own Project into a Playground
Cut Transparent Hole in Uiview
A Swift Example of Custom Views For Data Input (Custom In-App Keyboard)
Detect When an iOS App Is Launched For the First Time
How to Play a Local Video With Swift
Ios Universal Links Are Not Opening In-App
Run Code Only After Asynchronous Function Finishes Executing
How to Use Uiview Autoresizingmask Property Programmatically