2

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?

2 Answers 2

4

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
Sign up to request clarification or add additional context in comments.

Comments

3
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];

1 Comment

Thanks. That makes sense. Should have figured it out myself since Instruments showed me the line where the problem was.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.