Still getting used to this whole memory management thing. I thought I had it pretty much squared away, but just ran into a spot where I got complacent and let a bunch get by me.
I’ve been working mostly with cocos2d, where most of the objects are created with a static “node” or “spriteWith…” type methods, which return an autoreleased instance of the class you want.
[c]mySprite = [Sprite spriteWithFile:@”some.png”];
myScene = [SomeScene node];[/c]
Since these autoreleased, you need to immediately do something that’s going to retain them if you want to keep them around. In most cases, with something like a sprite, you are adding the sprite as a child of another object, which retains the sprite that is added. In some cases, you might be adding it to an array, which also retains it. In these cases, that’s usually all you want to do, because then when you remove it from the parent or the array, or release the array or whatever, the instance is released and dealloc’ed.
[c]mySprite = [Sprite spriteWithFile:@”some.png”]; // autorelease
[myScene addChild:mySprite]; // retains mySprite
… later …
[myScene removeChild:mySprite]; // releases mySprite. retainCount reaches 0, and it’s dealloced[/c]
Of course, if you want the sprite to stick around after removing it from the parent, retain it explicitly. Also, if you are NOT immediately adding the autoreleased object to something that will retain it, you will need to explicitly retain it:
[c]mySprite = [[Sprite spriteWithFile:@”some.png”] retain];[/c]
Now it will stick around, so you can add it to a parent object later. But remember, when you remove it from the parent, it will still have a retainCount of 1, so when you are really done with it, you’ll need to give it one more release:
[c][mySprite release];[/c]
So this brings me to where I messed up. Again, I got really used to these autoreleased objects. But then I created several “regular” objects for my model, using alloc/init, and added these to an array. I was stuck in autorelease mode, so I was doing something like this:
[c]myModelObject = [[ModelObject alloc] init];
[modelArray addObject:myModelObject];[/c]
Of course, when you alloc something, it gets a retain count of 1. This is not autoreleased. Adding it to the array, retains it again. Later, I was removing the object from the array, thinking that was the end of that. But it still had a retain count of 1. I wound up with a bunch of model objects never getting dealloced. Worse, these model objects were holding a reference to the view (I know, I know, questionable architecture to say the least), so even after the view (which was a cocos2d sprite) was removed from its parent, it was still being retained by the model and was sticking around, eating up not only memory, but running animations behind the scenes. The solution was simple:
[c]myModelObject = [[ModelObject alloc] init];
[modelArray addObject:myModelObject];
[myModelObject release][/c]
You’re safe to release it here, because the array is retaining it.
Now, this wasn’t some blinding realization. As soon as I saw the alloc/init without a release, it was a “duh!” moment. Found these in several places, cleaned them up and got a stellar performance boost. (Now I’m going to clean up that model/view connection.)
Anyway, not really sure what the point of this is. Just one of those things where you’re happy to find something big that really helps your app and you want to share it with someone. My wife and daughter didn’t really care. 🙂
Well I haven’t started playing with cocos2d yet but I’ll be sure to keep this in mind. And we care, so thanks 🙂
Keith, I’m interested to see how you came to this conclusion and what made you research it in the first place. Did you notice an overage of memory in the Performance Tool? How are you measuring memory usage and viewing the effects of your optimization?
Chuck, basically, it’s a game where a wave of enemies are created and you kill them. I was getting 60 fps in the first few waves, but the fps continued to drop in further waves, even after all enemies were killed. That told me that even though the enemies were removed from the screen, they were likely still consuming resources.
It’s hard to use the debugger to look for something that you don’t have a reference to. So I just tried logging the retainCount of each enemy at the point when it should have been removed. It should have been 1, meaning that once it was removed, retainCount would be 0, and it would be deallocated. But it turned out that the retainCount was higher. Just took a while to track down where it was being retained and not released. One release was supposed to be happening in the model object’s dealloc method. And checking into that, I found that that dealloc method was never being called. So that was the culprit holding onto the enemy view. So I had to find out what was holding on to that. This led me to the discovery above.
Once the model object was being correctly released, its dealloc was being called, giving the final release to the enemy view, allowing it to be dealloced.
Like hello, get a life… July 4th?
Tee hee!
🙂
No Seriously, THANK YOU! You have always provided solid insight in actionscript, flex and now this. When im looking for something and i find it here, i never question that its going to be a worthwhile read.
I just went through your Gravity Tutorial. Sure wish they hooked the mac accelerometer up to the simulator. I was hoping you missed it and was shaking my macbookpro around futilely trying to get it to work.