Effective Objective-C 2.0

Use Zombies to Help Debug Memory-Management Problems

Cocoa’s “zombies” feature can come in handy. When this debugging feature is enabled, the runtime turns all deallocated instances into a special zombie object rather than deallocating them. The core memory where the object is located is not made available for reuse; therefore, nothing will ever overwrite it. When it receives any message, a zombie object throws an exception saying exactly what message was sent and what the object used to be when it was still alive. Using zombies is the best way to debug memory-management problems.

enable zombies

  1. For example, if you’re using bash and running an application on Mac OS X, you would do something like this:

     export NSZombieEnabled="YES"
    
  2. It is also possible to turn on the option in Xcode such that the environment variable is automatically set when the application is run from within Xcode.

how does the zombies feature work?

So how does the zombies feature work? It is implemented deep within the Objective-C runtime and the Foundation and CoreFoundation frameworks. When an object is being deallocated, an additional step is made by using the environment variable if this feature is enabled. This extra step turns the object into a zombie rather than fully deallocating it.

```
void PrintClassInfo(id obj)
{
    Class cls = object_getClass(obj);
    Class superCls = class_getSuperclass(cls);
    NSLog(@"=== %s : %s ===", class_getName(cls), class_getName(superCls));
}

int main(int argc, char *argv[])
{
    EOCClass *obj = [[EOCClass alloc] init];
    NSLog(@"Before release:");
    PrintClassInfo(obj);
    [obj release];
    NSLog(@"After release:");
    PrintClassInfo(obj);
}

Before release:
=== EOCClass : NSObject ===
After release:
=== _NSZombie_EOCClass : nil ===
```

The object’s class has changed from EOCClass to _NSZombie_EOCClass. This uses the powerful runtime functions that can manipulate the class list.

```
// "Deallocating" an object pseudocode with zombies turned on
// Obtain the class of the object being deallocated
Class cls = object_getClass(self);

// Get the class’s name
const char *clsName = class_getName(cls);

// Prepend _NSZombie_ to the class name
const char *zombieClsName = "_NSZombie_" + clsName;

// See if the specific zombie class exists
Class zombieCls = objc_lookUpClass(zombieClsName);

// If the specific zombie class doesn’t exist, then it needs to be created
if (!zombieCls)
{
    // Obtain the template zombie class called _NSZombie_
    Class baseZombieCls = objc_lookUpClass("_NSZombie_");

    // Duplicate the base zombie class, where the new class’s name is the prepended string from above
    zombieCls = objc_duplicateClass(baseZombieCls, zombieClsName, 0);
}

// Perform normal destruction of the object being deallocated
objc_destructInstance(self);

// Set the class of the object being deallocated to the zombie class
objc_setClass(self, zombieCls);

// The class of `self’ is now _NSZombie_OriginalClass
```

Crucially, the memory the object lives in is not freed (through a call to free()); therefore, the memory will not be available for use again. Although this is leaking memory, this is a debugging tool only and would never be turned on for production- running applications, so it doesn’t matter.

Creating a new class is done by using the runtime’s function objc_duplicateClass(), which copies the entire class but gives it a new name. The superclass, instance variables, and methods of the duplicate class will be identical to the one being copied.

Another way to achieve the same goal of maintaining the old class name would be to create the new class as inheriting from NSZombie rather than copying it. However, the functions to do this are less efficient than performing a direct copy.

The _NSZombie_class (and therefore all its copies) do not implement any methods. The class does not have a superclass and is therefore a root class, just like NSObject, with a single instance variable, calledisa, which all Objective-C root classes must have. This lightweight class does not implement any methods, so whenever it is sent any message, it will go through the full forwarding mechanism (seeItem 12).

// Forwarding mechanism when a zombie is detected
// Obtain the object’s class
Class cls = object_getClass(self);

// Get the class’s name
const char *clsName = class_getName(cls);

// Check if the class is prefixed with _NSZombie_
if (string_has_prefix(clsName, "_NSZombie_")
{
    // If so, this object is a zombie

    // Get the original class name by skipping past the _NSZombie_, i.e. taking the substring from character 10
    const char *originalClsName = substring_from(clsName, 10);

    // Get the selector name of the message
    const char *selectorName = sel_getName(_cmd);

    // Log a message to indicate which selector is being sent to which zombie
    Log("*** -[%s %s]: message sent to deallocated instance %p", originalClsName, selectorName, self);

    // Kill the application
    abort();
}

At the heart of the full forwarding mechanism is forwarding, a function you may have seen in backtraces while debugging. One of the first things that this function does is check the name of the class of the object being sent a message. If this name is prefixed with NSZombie, a zombie has been detected, and something special happens. The application is killed at this point, after printing out a message (shown at the start of this item) to indicate what message was sent and to what type of class. Pseudocode showing what happens is as follows: