Dapple 1.2
I haven’t been posting very frequently lately, mostly because I’ve been working very hard to get this Dapple 1.2 update out the door. I’m thrilled to announced that I submitted it this afternoon! Dapple and Dapple Lite 1.2 were both submitted today. If everything goes according to plan, they should be available around this time next week.
The update is quite exciting, in that it adds support for global high scores lists! Now you can see how your top score compares to other people around the world! With the update you’ll be able to see your score and the 9 people ranked directly above you, or look at the top overall scores. It’s already been exciting watching the testers’ scores roll in. I can’t wait to see how everyone does!
Dapple Lite also allows you to submit your scores online, but you’ll be using the same scoreboards as those with the full version. If you want to get near the top of the scoreboards, you’ll have to buy the full version.
There are also a few user interface tweaks that I’ve made and also a few little fixes. Finally, there’s also a feature for a top secret project I’ve been involved in. You’ll have to wait for the update to find out what it is!
Leaderboards
Now I want to talk a little bit about the technology that I used for the high scores lists and then talk about a nasty memory leak I tracked down today before submitting. If you’re not interested in this kind of thing, I’ll forgive you if you want to stop reading now…
When I started thinking about implementing the leaderboards, I first looked a few existing technologies, hoping that I might be able to just “drop something in” and it would work. There are a few technologies out there that looked like the might be able to do that. Where they fell short, however, was in my ability to completely customize how I wanted to store the data and display it. Plus, I thought it would be fun to write my own system.
When I started reading up on the Google App Engine, I was convinced it would be easy to set up a server back-end using that, and I was right. Using the App Engine, I had the server-side working in about 3 days of work (and that included learning Python and how the App Engine works). It’s really easy to get things running with their system.
What took time, as usual, was the user interface. I had considered creating an in-game web view that would load a web page that had the high scores on it, but I decided (perhaps somewhat foolishly) that I didn’t want the player to have to leave the look and feel of the game. I decided to build it using my existing UI framework. This only caused a few problems, mostly around trying to find space to fit all the data. However, I also ran into some nasty string memory stuff in the code I wrote to parse the data coming from the server.
I encountered a memory leak this morning that I want to share with you, because it’s not immediately obvious why it’s a leak. When I get my data from the server, it comes in as a long string of data. I use NSString’s handy methods to chop it up and stuff the results into a dictionary, then I shove those dictionaries into a big array. Here’s a bit of the code that had the mem leak:
NSArray* recordData = [[records objectAtIndex:i]
componentsSeparatedByString:@","];
NSString* username =[[NSString alloc] initWithString:
[recordData objectAtIndex:0]];
...
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject:username forKey:@"name"];
...
[mCachedGlobalHighScores addObject:dict];
[username release];
This causes a memory leak that is caused at first line of code. I was baffled by what was causing this. The memory leak seemed to be happening on the string inside [recordData objectAtIndex:0] but I couldn’t figure out why. I asked around on Twitter and many people pointed me to the method [myObj retainCount] that allows you to see how many retains have been made on an object. BunnyHero pointed out that you can run this from the GDB command prompt when stopped at a breakpoint: ‘print (int)[myObj retainCount]‘.
I set breakpoints on the line where username is allocated, and on the line after. When execution halted just before username was allocated, I checked the retain count on the object at index 0. It was 1. This was expected. However, right after username was allocated, it became 2! Why was allocating a new string using initWithString (which is supposed to copy the string and create a new object altogether) increasing the ref count?!
I think it had something to do with the fact that the NSArray is storing the values as type id, not NSString. When you do an initWithString, I think it was just copying the pointer, and doing a retain, so the new object allocated memory, but pointed to the same object. So, in order to fix that, I had to do this instead:
NSArray* recordData = [[records objectAtIndex:i]
componentsSeparatedByString:@","];
NSString* username =[[NSString alloc] initWithFormat:@"%@",
[recordData objectAtIndex:0]];
Now the memory leak no longer happened when recordData was created…it happened when username was created! But that’s ok, now at least the leak is happening at a more reasonable place.
So now I looked at where I was cleaning up my big array of cached data:
[mCachedGlobalHighScores removeAllObjects];
Reading the docs, removeAllObjects is supposed to call release on each removed object. I assumed that would do a deep release. Not the case. So I tried this instead:
for (int i = 0; i < [mCachedGlobalHighScores count]; i++)
{
NSMutableDictionary* record =
[mCachedGlobalHighScores objectAtIndex:i];
[record removeAllObjects];
}
[mCachedGlobalHighScores removeAllObjects];
But that still didn’t work. The username string wasn’t been released! So finally, I did this:
for (int i = 0; i < [mCachedGlobalHighScores count]; i++)
{
NSMutableDictionary* record =
[mCachedGlobalHighScores objectAtIndex:i];
NSString* name = [record objectForKey:@"name"];
[name release];
[record removeAllObjects];
}
[mCachedGlobalHighScores removeAllObjects];
And it works! You might be asking why I didn’t do an autorelease on the username NSString. I tried that. It worked most of the time. But if you flipped back and forth on the screen quickly (causing everything to delete and recreate quickly), I found that I could get into situations where sometimes that string wouldn’t release at all, causing a leak. I decided that if I managed the memory myself, at least I’d know what’s going on.
So, the lessons are:
1) If you are dealing with a string that’s stored as type id, make sure you’re careful about how you use it to init new strings.
2) When you alloc (or retain) you have to release. (Yes, I know that should be obvious by now, but sometimes you think a built-in method is releasing or retaining when it isn’t).
Well, apparently I’ve decided to make up for a lack of posts with one gigantic post. Look for more news on the Dapple 1.2 update when it gets approved. And keep your eyes and ears open for news on that secret project I may or may not have mentioned…
Owen