If an object gets deallocated too early, other objects still might have references to it and accessing it will lead to crash. Enabling Zombie objects will prevent crashes because objects are never actually deallocated and we’ll get an exception if we try to access such an object. But figuring out which object is causing trouble is just smaller part of the job. We need to find out where is the extra release (or missing retain) that is causing troubles.

In smaller projects or when objects in question are localized to a smaller number of classes, we can find the bug by static code investigation. But if the object is used in a larger scope and often conditionally, we might want to take a look at what is going on in runtime.

To trace memory management related calls to our class, we will need to expand retain, release and autorelease methods. Simplest variant will just call super method, so we can put a breakpoint and see where is it called from. This can be useful, but usually there are lots of instances of the same class and lots of calls to those methods, so this isn’t really practical. It would be much more practical to log all calls to retain/release/autorelease and then dump the data when we need it. We can save three types of information to our log – current event (is it release, retain or autorelease), current time and what is most important, call stack trace so we know how exactly did we end up here. All this information can be stored into a simple array which will we add to the class we want to trace (of course, we want to allocate and release this array where appropriate).

NSMutableArray* _memTrace;

We can fill the _memTrace array with a call to the following method:

-(void) addMemTraceEvent:(NSString*)event
{
    [_memTrace addObject:[NSString stringWithFormat:@"%@ %@ at %@",
                          [NSDate date],
                          event,
                          [NSThread callStackSymbols]
                          ]];
}

We are using [NSDate date] to get current date and time and [NSThread callStackSymbols] to fetch current call stack trace.

To dump _memTrace, we can use following code:

-(void) dumpMemTrace
{
    NSLog(@"%@", _memTrace);
}

We can now update our memory management methods, so they log all calls to them:

-(void)release
{
    [self addMemTraceEvent:@"release"];

    [super release];
}

-(id)retain
{
    [self addMemTraceEvent:@"retain"];

    return [super retain];
}

-(id)autorelease
{
    [self addMemTraceEvent:@"autorelease"];

    return [super autorelease];
}

Now, when such updated object makes a Zombie access exception, we can simply call -dumpMemTrace from gdb’s command window, examine the log and find the extra release.

0 comments