Reputation: 441
I'm puzzled about iOS memory management. I have a class that has a member of type NSMutableArray. When storing objects of this type in another array and removing them, Instruments shows that all those members leak memory. Here's the definition of my rogue class:
@interface Tester : NSObject {
int some;
NSMutableArray* others;
}
@property int some;
@property (nonatomic, retain) NSMutableArray* others;
-(id)init;
-(id)copy;
-(void)dealloc;
@end
Here's the implementation of the rogue class:
@implementation Tester
@synthesize some;
@synthesize others;
-(id)init {
self = [super init];
if(self) {
some = 0;
others = [[NSMutableArray alloc] initWithCapacity:5];
int i;
for(i = 0; i < 5; ++i) {
[others addObject:[NSNumber numberWithInt:i]];
}
}
return self;
}
-(id)copy {
Tester* cop = [[Tester alloc] init];
cop.some = some;
cop.others = [others mutableCopy]
return cop;
}
-(void)dealloc {
[others removeAllObjects];
[others release];
[super dealloc];
}
@end
And here's how I test it:
NSMutableArray* container = [[NSMutableArray alloc] init];
Tester* orig = [[Tester alloc] init];
int i;
for(i = 0; i < 10000; ++i) {
Tester* cop = [orig copy];
[container addObject:cop];
}
while([container count] > 0) {
[[container lastObject] release];
[container removeLastObject];
}
[container release];
Running this code leaks memory and Instruments shows that the leaked memory is allocated at line:
cop.others = [others mutableCopy];
What have I done wrong?
Upvotes: 2
Views: 3160
Reputation: 81878
You are creating a copy: [others mutableCopy]
which you own but forget to release. The line should be:
cop.others = [[others mutableCopy] autorelease];
You testing code would be clearer if you'd let the container
array be the sole owner of the Tester
objects:
NSMutableArray* container = [[NSMutableArray alloc] init];
Tester* orig = [[Tester alloc] init];
for (int i = 0; i < 10000; ++i)
[container addObject:[[orig copy] autorelease]];
while([container count] > 0)
[container removeLastObject];
[container release];
Now you could remove the loop that empties the container.
Or you could just skip the container:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Tester* orig = [[[Tester alloc] init] autorelease];
for (int i = 0; i < 10000; ++i)
[[orig copy] autorelease];
[pool drain]; // At this point all Tester objects are released
Upvotes: 4
Reputation: 13622
cop.others = [others mutableCopy]
Others is declared as a retained property, so assigning to it establishes an ownership claim on the new value. -mutableCopy is a method that implies ownership (because it contains the word "copy"). So you now have two claims of ownership, both of which must be released. The recommended way to do this is to assign the copy first to a temp variable, then assign that to your property and release it, like so:
NSMutableArray *tmpArray = [others mutableCopy];
cop.others = tmpArray;
[tmpArray release];
You could also do this in one step, avoiding the temp object, although doing so uses the autorelease pool and is slightly less efficient because of that:
cop.others = [[others mutableCopy] autorelease];
Upvotes: 3