Monday, June 15, 2009

NSMutableArray and removeObjectAtIndex:

I'm working on some iPhone code that removes objects at random from an NSMutableArray and started to wonder about the lifecycle of items in a collection. I know that the collections in the Foundation library retain objects when they're added and release them when the collection deallocs itself, so it makes sense that calling removeObjectAtIndex: or any of the other "remove" methods would also release the object in question.

The removeObjectAtIndex: method doesn't return the removed object to you -- if you want to hold on to it, you need to call objectAtIndex: first. It also makes sense that you need to retain the object you're holding on to. A quick inspection of my code showed that I wasn't doing that in at least one place, but my app wasn't crashing. This made me wonder if objectAtIndex: was autoreleaseing the object it returned.

A quick Google search turned up an entry in William Woody's Development Chaos Theory blog entitled The lifespan of an object owned by NSMutableArray, or why Objective-C annoys me and Java makes me happy.

The punchline?

MyObject *obj = [myMutableArray objectAtIndex:0];
[[obj retain] autorelease];  // before removeObjectAtIndex
[myMutableArray removeObjectAtIndex:0];
UseMyObject(obj);

You need to retain the item before you remove it from the NSMutableArray (and eventually release or autorelease it).

The moral of the story: NSString literals are long lived and don't become invalid when a collection releases them. If you build your NSMutableArray by doing something like this:

NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithObjects:@"one", @"two", @"three", nil];

You will mask a lot of memory management problems.

1 comment:

Keith Peters said...

Good points. Literal strings are never released:

NSString *str1 = @"i am a string";
NSString *str2 = [[NSString alloc] initWithFormat:@"i am a %@", @"string"];
NSLog(@"str1 %i", [str1 retainCount]);
NSLog(@"str2 %i", [str2 retainCount]);
[str1 release];
[str1 release];
[str1 release];
[str1 release];
[str1 release];
[str1 release];
[str1 release];
NSLog(@"str1 %i", [str1 retainCount]);

str1 will have a retain count of 1.
str2 has 2147483647, and it never changes.