Chapter 15: Finishing Touches

Although all of the coding is complete, and everything seems to be running well, there is one last thing we need to do before we can consider this the end of our project. We need to check the code for memory leaks.

Apple has made it so easy to run diagnostics on your iPhone code that I would be remiss if I didn’t cover the Instruments tool as a part of this tutorial.

Instruments is a diagnostic tool that watches your iPhone application run and reports potential problems with various memory, I/O, disk, and other device and Operating System functions. We’re only going to look for memory leaks in this tutorial, but once you know how to do that, adding additional reports is easy.

First, go back into Xcode and click on the ‘Product’ menu, then select the ‘Profile’ menu item. The code will compile and the Instruments launch screen will pop up.

 

 

Select the ‘Leaks’ item and click on the ‘Profile’ button. The application will run and the main Instruments application window will appear.

 

 

I can already see from the second line at the top, ‘Leaks’, that we have some issues to look at. In the iPhone emulator, let’s play one quick round of our game and come back to instruments.

Once the round is complete, return to Instruments and click on the ‘Stop’ button in the upper left-hand corner of the Instruments application window. Once Instruments has been stopped, click on the ‘Leaks’ box in the ‘Instruments’ column on the left. The lower display will populate with a list of possible memory leaks.

 

 

Let’s see what problems we might have in our code. Some of the problems listed in this window may be results of other problems in our code, and may not even be available to browse because they’re in modules that we don’t have source code for. We’ll start by looking for a problem that’s been reported in one of our classes.

 


 

The second entry in the list indicates a problem in the TouchTargetsViewController, so let’s check that one out. If I double click in the ‘#’ column, it will take me to the source code with a notation on the problematic lines. All I need to do is scroll through the code until I see the notations.

 

 

Here’s one problem. Let’s take a closer look at that line of code. If you’re going to be going back into Xcode to look at code, it may help to turn line numbers on from the Xcode preferences pane, in the ‘Text Editing’ section.

        [myScoreMessage setString:[[NSString alloc] initWithFormat:@"Score: %d", gameScore]];

Our first clue as to why we might have a problem here is the ‘alloc’ message to NSString. Let’s look at the setString: method in the EDTextString class.

- (void)setString:(NSString *)newTextString {
    if(textureCoordArray != NULL) {
        free(textureCoordArray);
        textureCoordArray = NULL;
    }

    myTextString = newTextString;
   
    // Changing a text string will result in it being alive again, fully opaque, and
    // set to 'live forever'.
   
    alive = TRUE;
    alpha = 1;
    lifespan = 0;
}

When we pass in the string that we allocated previously, it’s assigned directly to our instance variable. Assigning something directly to an instance variable does not release whatever object was held by that instance variable previously, so we’re leaking.

If you’re not familiar with the term ‘release’, here’s how it works: when you allocate an object in your code, you own it. The way that objective-c establishes ownership is through the use of a ‘retain count’. Allocating a new instance of something, like we’re doing in the call to EDTextString’s setString: method, sets the retain count to 1. When we’re finished with the objects we allocate, we need to ‘release’ them to decrement the retain count. When the retain count reaches zero, the object is destroyed and the memory it took up is freed for other things.

What Instruments is pointing out to us is the fact that we’re incrementing the retain count by allocating the new string, but never releasing it anywhere.

If we were using getter and setter methods generated by Xcode (by using the @property and @synthesize directives), we could use the autorelease keyword, which would decrement the retain count from the allocation, relying on the setter method to use a retain message to increment the count again. Since we’ve coded our own method to do the instance variable assignment, however, we’re going to have to handle this ourself.

We can fix this problem by explicitly releasing whatever string was referenced by our instance variable before making the new assignment, like this:

- (void)setString:(NSString *)newTextString {
    if(textureCoordArray != NULL) {
        free(textureCoordArray);
        textureCoordArray = NULL;
    }
   
    [myTextString release]; // ED: Added

    myTextString = newTextString;
   
    // Changing a text string will result in it being alive again, fully opaque, and
    // set to 'live forever'.
   
    alive = TRUE;
    alpha = 1;
    lifespan = 0;
}

With that new code in place, let’s see what else was wrong with the view controller.

 

 

Instruments doesn’t like these assignments, either. These statements are in the touchesBegan:withEvent: method, where we’re creating a new target and popping up score text when a player hits a target.

Let’s look at the code again.

        // Game is NOT over, so check to see if we hit a target
       
        EDTarget *target = [myTargetManager didHitTargetAtX:p.x andY:p.y];
       
        if(target != nil) {
            [target destroy]; // Remove hit target from game
           
            gameScore += 500;
            [myScoreMessage setString:[[NSString alloc] initWithFormat:@"Score: %d", gameScore]];
           
            if(myTargetLifespan > 5) {
                myTargetLifespan -= 5; // Decrease global lifespan
            }
           
            // Create new target to take hit target's place
           
            int targetSize = 64;  // Target width and height will match
           
            // The targets are centered, so adjust the position
            // by half of the width and height
           
            int minX = targetSize / 2;
            int maxX = myScreenWidth - (targetSize / 2);
           
            int minY = targetSize / 2 + 25; // Account for score area at top of screen
            int maxY = myScreenHeight - (targetSize / 2);
           
            int randomX = arc4random() % (maxX - minX + 1) + minX;
            int randomY = arc4random() % (maxY - minY + 1) + minY;
           
            target = [[EDTarget alloc] init];
           
            [target setPositionX:randomX andY:randomY andZ:0];
            [target setLifespan:myTargetLifespan withDecayAt:(myTargetLifespan / 2)];
            [target setTargetWidth:targetSize andHeight:targetSize];
           
            [myTargetManager addTarget:target];
           
            // Pop up some floating text to let player know that the hit
            // was registered
           
            EDTextString *hitScore = [[EDTextString alloc] initWithString:@"+500"];
           
            [hitScore setColorRed:0 andGreen:128 andBlue:0];
            [hitScore setPositionX:p.x andY:p.y andZ:0];
            [hitScore setLifespan:100 withDecayAt:100];
            [hitScore setDriftX:0 andY:-1 andZ:0];
            [hitScore setSize:15];
           
            [myTextStringManager addTextString:hitScore];

This problem looks similar to our first one. We’re allocating and initializing an EDTarget object in the first case, and an EDTextString object in the second case. Both statements will increment the retain counter, but so will assigning it to our manager objects. When it’s released from the respective manager objects, the retain counters will be decremented, but will still be one from the initial allocation.

When you have a case where you need to allocate something to a local variable (that will increment the retain count) and then hand it off to something that will make an assignment (that will also increase the retain count), you can use the autorelease keyword to let iOS know that you don’t need to keep a retain count from the initial allocation. When the object that took it from us is finished with it, it’s safe to destroy.

Let’s see what the new code would look like.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *t = [[touches allObjects] objectAtIndex:0];
    CGPoint p = [t locationInView:self.view];
   
    if(gameOver == TRUE) {
        // Game is over, user is touching to start. Generate five
        // targets in random locations.
       
        if(gameOverCounter > 0) {
            return;
        }
       
        gameScore = 0;
        [myScoreMessage setString:[[NSString alloc] initWithFormat:@"Score: %d", gameScore]];
       
        gameOver = FALSE;
        myTargetLifespan = 250;
       
        EDTarget *target;
       
        for(int i = 0; i < 5; i++) {
            //target = [[EDTarget alloc] init]; // ED: Removed
            target = [[[EDTarget alloc] init] autorelease]; // ED: Added
           
            int targetSize = 64;  // Target width and height will match
           
            // The targets are centered, so adjust the position
            // by half of the width and height
           
            int minX = targetSize / 2;
            int maxX = myScreenWidth - (targetSize / 2);
           
            int minY = targetSize / 2 + 25; // Account for score area at top of screen
            int maxY = myScreenHeight - (targetSize / 2);
           
            int randomX = arc4random() % (maxX - minX + 1) + minX;
            int randomY = arc4random() % (maxY - minY + 1) + minY;
           
            [target setPositionX:randomX andY:randomY andZ:0];
            [target setLifespan:myTargetLifespan withDecayAt:(myTargetLifespan / 2)];
            [target setTargetWidth:targetSize andHeight:targetSize];
           
            [myTargetManager addTarget:target];
        }
       
        // Also make "Touch to Start" text fade away.
       
        [myStatusMessage setLifespan:100 withDecayAt:100];
    } else {
        // Game is NOT over, so check to see if we hit a target
       
        EDTarget *target = [myTargetManager didHitTargetAtX:p.x andY:p.y];
       
        if(target != nil) {
            [target destroy]; // Remove hit target from game
           
            gameScore += 500;
            [myScoreMessage setString:[[NSString alloc] initWithFormat:@"Score: %d", gameScore]];
           
            if(myTargetLifespan > 5) {
                myTargetLifespan -= 5; // Decrease global lifespan
            }
           
            // Create new target to take hit target's place
           
            int targetSize = 64;  // Target width and height will match
           
            // The targets are centered, so adjust the position
            // by half of the width and height
           
            int minX = targetSize / 2;
            int maxX = myScreenWidth - (targetSize / 2);
           
            int minY = targetSize / 2 + 25; // Account for score area at top of screen
            int maxY = myScreenHeight - (targetSize / 2);
           
            int randomX = arc4random() % (maxX - minX + 1) + minX;
            int randomY = arc4random() % (maxY - minY + 1) + minY;
           
            //target = [[EDTarget alloc] init]; // ED: Removed
            target = [[[EDTarget alloc] init] autorelease]; // ED: Added
           
            [target setPositionX:randomX andY:randomY andZ:0];
            [target setLifespan:myTargetLifespan withDecayAt:(myTargetLifespan / 2)];
            [target setTargetWidth:targetSize andHeight:targetSize];
           
            [myTargetManager addTarget:target];
           
            // Pop up some floating text to let player know that the hit
            // was registered
           
            //EDTextString *hitScore = [[EDTextString alloc] initWithString:@"+500"]; // ED: Removed
            EDTextString *hitScore = [[[EDTextString alloc] initWithString:@"+500"] autorelease]; // ED: Added
           
            [hitScore setColorRed:0 andGreen:128 andBlue:0];
            [hitScore setPositionX:p.x andY:p.y andZ:0];
            [hitScore setLifespan:100 withDecayAt:100];
            [hitScore setDriftX:0 andY:-1 andZ:0];
            [hitScore setSize:15];
           
            [myTextStringManager addTextString:hitScore];
        } else {
            // No target was hit, so display a penalty
           
            gameScore -= 100;
            [myScoreMessage setString:[[NSString alloc] initWithFormat:@"Score: %d", gameScore]];
           
            //EDTextString *hitScore = [[EDTextString alloc] initWithString:@"-100"]; // ED: Removed
            EDTextString *hitScore = [[[EDTextString alloc] initWithString:@"-100"] autorelease]; // ED: Added
           
            [hitScore setColorRed:128 andGreen:0 andBlue:0];
            [hitScore setPositionX:p.x andY:p.y andZ:0];
            [hitScore setLifespan:100 withDecayAt:100];
            [hitScore setDriftX:0 andY:-1 andZ:0];
            [hitScore setSize:15];
           
            [myTextStringManager addTextString:hitScore];
        }
    }    
}

If you browse through the code, you’ll see where we added the ‘autorelease’ keyword anywhere we were allocating new objects with local variables before adding them to manager objects. Let’s see what Instruments thinks about our code now.

 

 

Gah! What happened, did we make it worse?!

Don’t worry, plugging memory leaks with Instruments can be an iterative process. I’ve sorted by ‘Responsible Frame’ so we can narrow the problems down to our own objects, which are the source of all of the other problems. Let’s start with the top one, a problem in EDFontImporter.

 

 

Looks like we have another object we allocated that needs to be released. Let’s go into this code and add a release at the bottom.

- (void)loadCharacterPage:(NSString *)characterPageName {
    myCharacterPageName = characterPageName;
   
    int characterLineBounds[16];    // 8 lines, 2 boundaries per line
    int currentCharacterLine = 0;   // Current line boundary
    BOOL inCharacterLine = FALSE;   // Are we inside a row of characters?
   
    // After reading in the graphics file with characters, scan from top to
    // bottom and record top and bottom boundaries for each line
   
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:myCharacterPageName ofType:nil]];
    CGImageRef imageRef = [image CGImage];

        // All finished, free memory used to store image
       
        free(imageRef);
    }
}

All we need to do is add an [image release] statement and we should be good. The new code should look like this:

        // All finished, free memory used to store image
        [image release];
       
        free(imageRef);
    }
}

Let’s run it through Instruments again and see what happens.

What? It crashed?

We’d better take another look at that code. Something doesn’t look right. Why are we calling free() on the imageRef variable? Let’s look at some more code.

    if(imageRef) {
        size_t imageWidth = CGImageGetWidth(imageRef);
        size_t imageHeight = CGImageGetHeight(imageRef);
       
        // Save the width and height
       
        characterPageWidth = imageWidth;
        characterPageHeight = imageHeight;
       
        GLubyte *imageData = (GLubyte *)malloc(imageWidth * imageHeight * 4);
        memset(imageData, 0, (imageWidth * imageHeight * 4));
       
        CGContextRef imageContextRef = CGBitmapContextCreate(imageData, imageWidth, imageHeight, 8, imageWidth * 4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);

It looks like we found not only a memory leak, but a bug as well. We should have been calling free() on imageData, not imageRef. We’ll have to fix that too.

The correct code should look like this:

        // All finished, free memory used to store image
        [image release];
       
        //free(imageRef); // ED: Removed
        free(imageData); // ED: Added
    }
}

I think pretty much every crash I’ve ever seen while developing code on the iPhone has been related to errors I’ve made in memory management, like this one. Any others have been related to either forgetting to add resources to the main bundle, or misspelling the names of resources that were already there.

Let’s try running our corrected code through Instruments.

 


 

Looking better. Clicking through each of these leaks, I find the exact same thing.

 

 

This is from the EDTarget class.

 


 

This is also from the EDTarget class. The other two are the same type of malloc() calls from the EDTextString class. We can see in the code above that the array is always freed if it’s populated before populating it again, though, so what’s the problem? Maybe our code is just to technologically advanced for Instruments to handle?

I’ll wait for you to stop laughing.

Right, so we know that the array is allocated on demand, and we know that we’ve been careful to always check that it’s freed, if populated, before we allocate it again, so what did we miss?

What do the EDTarget and EDTextString classes have in common? We throw them away when we’re finished with them. When we throw them away, iOS destroys them. Where in our code are we cleaning up those arrays when we’re destroyed?

Oops, we’re not.

We need to add a little more code. When our object is destroyed, we get a dealloc message that allows us to clean up after ourselves, so we need to take advantage of that now.

In the EDTarget class, add the following method to the end of the class source.

// ED: Added method - Free resources

- (void)dealloc {
    if(vertexArray != NULL) {
        free(vertexArray);
        vertexArray = NULL;
    }
   
    if(textureCoordArray != NULL) {
        free(textureCoordArray);
        textureCoordArray = NULL;
    }
   
    [super dealloc];
}

The first two conditional blocks of code will free the vertex and texture coordinate arrays if they’re populated, and the [super dealloc] will call the dealloc class in our parent class. Don’t forget to do this whenever you override the dealloc method.

Next, at the end of the EDTextString class, we add the same code.

// ED: Added method - Free resources

- (void)dealloc {
    if(vertexArray != NULL) {
        free(vertexArray);
        vertexArray = NULL;
    }
   
    if(textureCoordArray != NULL) {
        free(textureCoordArray);
        textureCoordArray = NULL;
    }
   
    [super dealloc];
}

Let’s run the code through Instruments again and see how we’re doing.

 

 

Beautiful. I played five games and let Instruments monitor the whole thing. Instruments reported no memory leaks.

Now our project is complete.

I hope you enjoyed the TouchTargets tutorial, and wish you luck with your own apps!

Chapter 14 | Index