views:

242

answers:

3

Update: this leak has been solved. If you are getting similar leaks and your application is multi-threading, you are most likely making UIKit calls from a background thread; make use of e.g. [NSThread performSelectorOnMainThread:] to route UIKit calls out to the main thread, which is the only place where they are allowed.

I've been running Leaks on my current project to find leaks lately, and I keep running into these "leaks" which from what I can tell aren't really leaks. The following code, taken directly from the project, has two leaks, according to Leaks:

- (NSArray *)areaForIndex:(int)index 
{
    NSMutableArray *a = [NSMutableArray arrayWithArray:
        [world  retrieveNeighborsForIndex:index]]; // leak 1
    [a insertObject:[references objectAtIndex:index] atIndex:0];
    return [NSArray arrayWithArray:a]; // leak 2
}

Leak 1 goes away if I change the first line to: (see Update 2-3)

Leak 2 goes away if I change the last line to:

    return a;

I can't unfortunately do that with leak 1 because I'm casting an immutable array into a mutable one. Still, arrayWithArray is supposed to autorelease, anyway, so from what I can tell, it shouldn't leak anything. Any ideas why this is happening?

Update: I've tested this on both the device and the Simulator. The leak is there on both. However, on the Simulator I get some additional information about this leak:

The history for the leak goes as follows:

# | Category | Event Type  | Timestamp | RefCt |  Address  | Size | Responsible Library | Responsible Caller
--+----------+-------------+
0 | CFArray  | Malloc      | 00:09.598 |     1 | 0x474f6d0 |   48 | asynchro            | -[muddyGrid areaForIndex:]
1 | CFArray  | Autorelease | 00:09.598 |       | 0x474f6d0 |    0 | Foundation          | NSRecordAllocationEvent
2 | CFArray  | CFRetain    | 00:09.598 |     2 | 0x474f7d0 |    0 | Foundation          | -[NSCFArray retain]
3 | CFArray  | CFRelease   | 00:09.611 |     1 | 0x474f7d0 |    0 | Foundation          | NSPopAutoreleasePool

The things I can discern from the above is that the autoreleased array is somehow retained twice and then autoreleased, leaving retain count at 1. No idea where or why though...

Update 2 & 3: I tried changing the line for Leak 1 to:

    NSMutableArray *a = [[[NSMutableArray alloc] initWithArray:
        [world retrieveNeighborsForIndex:index]] autorelease];

I thought this removed the leak, but it didn't, ultimately. So I'm still at a loss.

A: 

What retrieveNeighborsForIndex returns?

Is there a chance that the result of this method is retained (not autoreleased)?

Michael Kessler
It is returning [neighbors allObjects], where neighbors is an NSMutableSet containing a number of objects (9-12).
Kalle
A: 

arrayWithArray retains the objects that you stored into your mutable array. http://www.iphonedevsdk.com/forum/iphone-sdk-development/14285-nsmutablearray-arraywitharray-does-add-retain.html

I would recommend releasing it, or safer yet create the objects with the autorelease attribute set. When calling retrive neighbors for index make those objects autorelease and they will be released when the nsmutable array is dealloc'ed

EDIT: One Tip I might add when tracking down memory bugs is to enable zombies, and check the reference count of your objects. You can then determine which ones are not being released and it should make your tracking easier. This link will show you how to setup your xcode project to enable zombies: cocoadev.com/index.pl?NSZombieEnabled

Shadow
It does, but the array returned by `arrayWithArray:` is itself autoreleased, so releasing it yourself will probably cause a crash. That said, it's probably safe to return the mutable array itself, since you're creating it and returning it in the same method, with no hidden side effects.
Wevah
That works for leak 2 (and it's what I'm doing now), but it doesn't solve leak 1, where I'm taking an immutable array into a mutable array using arrayWithArray, since I need to add to it before returning it.
Kalle
My response was to Wevah, by the way; Shadow's right in that `arrayWithArray:` retains the objects in the array, but it itself is not contained and shouldn't be released nor autoreleased.
Kalle
Your immutable array is not in the autorelease pool. Try adding the autorelease attribute to that array
Shadow
Shadow: maybe I'm missing something, but it's not being retained anywhere (it's returned as an autoreleased object) -- [NSSet allObjects] does not return a retained object.
Kalle
When things are added to an NSSet they receive a Retain. Just make sure that you clean up your mess when finished. Cocoa and it's memory model are frustrating. Biggest thing to check out is the different methods that create your object arrays. If those properties bump the reference count of your objects that is one reason that your memory leak is occurring. (This is assuming that the objects that are being added into your NSSet, Array whatever, are NOT autoreleased objects)
Shadow
Also run with zombies enabled and check the ref counts of you objects. You can then determine which ones are not being released and it should make your tracking easier. http://www.cocoadev.com/index.pl?NSZombieEnabled Should help you get that enabled.
Shadow
Shadow: thanks a lot for all your comments. I have tried zombies and I am dealing exclusively with autoreleased objects in this particular case, but I'll triple-check retain counts and such and comment back later today. :)
Kalle
A: 

Anticlimactically, this one solved itself as I solved a bunch of other issues with my code.

Mainly, the code had a number of places where UI updates were made outside of the main thread, which is a big no-no. One of these other unrelated issues must have triggered the memory leak in the above code, since it doesn't report any leaks anymore (I have 0 leaks as it stands), although I haven't modified the code any.

Kalle