Appendix A. Tools for Troubleshooting Your Applications

In this book, I have covered various topics related to building data-centric iPhone and iPad applications. Because most readers are already familiar with iOS software development, this book does not include in-depth coverage of the tools that experienced developers may already know about such as Instruments and the Static Analyzer.

This appendix covers these tools, which can be invaluable when troubleshooting your applications. The techniques that you learn in this appendix are general. You should be able to use these techniques as-is with the current version of Instruments, and apply the same principles to future versions of the tool. If you already know about these tools, perhaps you will learn a new tip or trick. If you have never used these tools, you will learn how to use them to effectively track down problems in your code.

INSTRUMENTS

Instruments is a graphical tool that helps you to gather information about your application at runtime. You can then use this information to help track down difficult bugs such as memory leaks. Instruments is also valuable in profiling the performance of your application and helping you to track down and fix bottlenecks. Many different tools are available in the Instruments application to help you to troubleshoot a variety of application problems.

The Instruments application consists of a set of instruments that you use to collect data about your application as it runs. You can see all of the instruments that are available for use in Figure A-1. The instruments generally display their results graphically. You can have many instruments running concurrently with their resulting graphs displayed together in the application interface. This can help you analyze the relationships between the data collected by different instruments.

Available instruments

Figure A.1. Available instruments

You can also create custom instruments that use DTrace to examine the execution of your application. DTrace is a dynamic scripting tool that Sun created and Apple ported to OS X.

You cannot use DTrace custom instruments on the iPhone OS directly, but you can use them in conjunction with the simulator when running your application on your development machine. While this reduces the usefulness of custom instruments because you cannot use them for performance profiling on the device, you can still build instruments to help you to debug your applications in the simulator.

Because DTrace instruments are of limited usefulness to iPhone developers, I will not cover them in detail. You can refer to the Instruments user guide located at http://developer.apple.com/iphone/library/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide for more details on creating custom instruments using DTrace.

Starting Instruments

You can start the Instruments tool by launching the application, which is located, by default, in /YourDriveName/Developer/Applications or by selecting Run

Starting Instruments

If you start the application directly, you will see a list of templates for the trace document that you are about to start. The trace document holds the set of all of your individual trace runs. You can save your trace documents so that you can review all of the data collected from your traces at any time. If you start Instruments from within Xcode, you are effectively selecting the template that you will use when you choose an option under Instruments in the menu bar.

The templates consist of default sets of instruments designed to assist you with specific tasks. For instance, you would select the Leaks template if you were interested in troubleshooting memory leaks in your application. After you select the Leaks template, the Instruments application appears with both the Leaks and ObjectAlloc instruments loaded into the trace document. If you examine the definitions of each tool, you will see that you often want to use the ObjectAlloc instrument in conjunction with the Leaks instrument because ObjectAlloc can give you insight into the history of an object that the Leaks tool reports as a leak.

The Trace Document

The trace document is the group of tools that you are using, along with any test runs. You can see an example of a trace document in Figure A-2.

The trace document

Figure A.2. The trace document

The Instruments pane shows the list of instruments that you are using for the current trace. Clicking the info button for an instrument displays that instrument's configuration options. You can add instruments to the Instruments pane by dragging them from the instruments library (displayed with Command+L) and dropping them into the Instruments pane.

The Track pane displays the graphical output of the instrument in a timeline. Each instrument records its data into its own track. The Track pane shows the time that an event occurred during the test run. You can adjust the time scale using the slider at the bottom of the Instruments pane. You can scroll the Track pane using the scrollbar at the bottom of the pane.

The small triangle that appears at the top of the Track pane is the playhead. The playhead indicates the current time of the test run. You can move the playhead to review the state of your application at any given time during its execution. You can add a flag at the current location of the playhead by clicking the flag icon with the plus sign next to it in the toolbar. You can navigate forward to the next flag or back to the previous flag by clicking the flag icons to the right or the left of the add flag icon respectively.

The Detail pane shows the details of the currently selected tool. The Detail pane shows different details based on the tool that you have selected in the Instruments pane. You can select an item in the Detail pane to view more detail about the item in the Extended Detail pane. The Extended Detail pane is particularly useful because it shows a stack trace for the method call that you have selected in the Detail pane.

I find it useful to display the file icons in the Extended Detail pane because doing so makes the calls in the stack that originated in your code obvious. You can enable the file icons by clicking on the gear icon in the Extended Details pane. If you double-click on an item in the call stack that corresponds to one of your source code files, Instruments will display the source code in the Detail pane. Instruments highlights the line of code that was executing when the sample was taken.

You can narrow the amount of data contained in the Detail pane by specifying an inspection range. To specify an inspection range, move the playhead to the location in the timeline where you want the range to begin and click on the left icon in the Inspection Range icon group in the toolbar. Next, move the playhead to the location in the timeline where you want the range to end and click on the right icon in the Inspection Range icon group in the toolbar. You should see the data in the Detail pane reduce down to include only the data collected during the time window specified by the inspection range that you have created.

Objective-C Memory Management

In the upcoming section, you will explore the use of Instruments to discover the cause of a memory leak. First, let's take a brief detour to make sure that you understand how memory management works in Objective-C.

Although garbage collection is a nice feature available to developers on the Mac platform, there is currently no garbage-collected version of the Objective-C runtime available for the iPhone or iPad. Therefore, you are responsible for managing the memory consumed by the objects that you create in your applications. If you fail to properly free the memory that you allocate, the total amount of memory consumed by your application will grow as the application runs. This failure to clean up unused memory results in a memory leak. Eventually, if your program consumes too much memory, the OS will terminate your application.

All Objective-C classes that inherit from NSObject have a retain count. When an object is allocated using alloc or new, its retain count is set to 1. The retain count is a counter that indicates the number of bits of code that are interested in the object. When you need to hold on to a reference to an object, you increment the retain count by calling the retain method on the object. When you are finished with an object, you call release to decrement the retain count. The retain count determines when the object should be de-allocated and its dealloc method called.

You need to be careful to balance calls to new, alloc, or retain with calls to release. If you have too few calls to release, the retain count for the object will never drop to 0 and the object will never be released, resulting in a memory leak. If you call release too many times, you will over-release the object, causing a segmentation fault and an application crash.

In general, adding an object to a collection such as NSMutableArray increments the retain count. Likewise, removing an object from a collection decrements the retain count. Simply obtaining an object from a collection typically returns an autoreleased object. If you need to hold on to the reference to an autoreleased object, you need to call retain on it.

You can also send the message autorelease to an object. This indicates that the runtime should release the object at a point in the future, but not right away. You use autorelease pools to keep track of all autoreleased objects. There is an application-wide autorelease pool that the project template creates automatically when you begin your application. You can create local pools yourself as well.

Autorelease is particularly useful for returning objects from methods. You can allocate the object that you plan to return from the method, configure it, and then autorelease it. It is then the caller's responsibility to retain the object to ensure that it has the proper retain count. The Objective-C runtime will send an autoreleased object to the release message one time for every time you call autorelease on it when the autorelease pool is drained or de-allocated.

When you create an object with a helper method that has alloc, new, or copy in its name, it is your responsibility to release it. Objects created in this way, by convention, have a retain count of 1. If you use a method that returns an object such as stringWithString to get an instance of an object, you should assume that the object is autoreleased.

If you need to hold on to a reference to an autoreleased object, it is your responsibility to call retain. The default autorelease pool will de-allocate autoreleased objects each time through the application's run loop. So, if you get an autoreleased object in a function, use it right away, and don't need it after the method call is complete, you do not need to worry about retaining it. However, if you plan to put an autoreleased object into an instance variable for access at a later time, you have to call retain on it or else it will be de-allocated at the end of the run loop and your application will crash with a segmentation fault when you try to send a message to the de-allocated object.

You should use release instead of autorelease whenever possible as there is less overhead in calling release. If you are going to be creating and autoreleasing many objects, in a loop perhaps, you should wrap the loop in its own autorelease pool.

You can send the retainCount message to any NSObject to obtain the current retain count of that object. You generally won't use this method in a production application, but it can be helpful to log the retain count of an object as you are trying to debug memory problems.

The rules of memory management are simple. To summarize:

  • If you create an object with alloc, new, or copy, the object will have a retain count of 1 and you are responsible calling release.

  • If you get a reference to an object in any other way, you can assume that it has a retain count of 1 and has been autoreleased. If you need to hold on to a reference to the object, retain it.

  • If you call retain on an object, you have to balance the call to retain with a call to release.

A memory leak occurs when you lose the reference to a pointer for an object that you have not deleted from the heap, or if you do not match the number of calls to retain, alloc, new, or copy to the number of calls to release. This is called over-retaining an object. Over-retaining will result in the object's retain count never reaching 0 at which time the runtime frees the memory consumed by the object. Because the object is not freed, you have leaked the memory consumed by the object for the remainder of the life of the program.

Sample Memory Leak Application

Now that you have an understanding of memory management and Objective-C, you will build an application with a memory leak and then use Instruments to find and fix that leak. The application will be very simple with only a single button for the user interface, as you can see in Figure A-3. When you tap the Go button, a routine with a memory leak will run and log to the console.

MemoryLeaker application

Figure A.3. MemoryLeaker application

Start a new View-based Application project for iPhone called MemoryLeaker. Open the MemoryLeakerViewController.h header file and add an action for the Go button as follows:

#import <UIKit/UIKit.h>

@interface MemoryLeakerViewController : UIViewController {

}

-(IBAction) goPressed:(id) sender;


@end

Now, you need to build the interface in Interface Builder. Open the MemoryLeakerViewController.xib file in Interface Builder. Add a UIButton to the View and wire the TouchUpInside event to File's Owner goPressed method. Save the XIB file and close Interface Builder.

Next, you are going to implement the goPressed method in the MemoryLeakerViewController.m implementation file. Remember that this code has a memory leak so don't use it in any of your applications. Here is the implementation of the goPressed method:

-(IBAction) goPressed:(id) sender
{

    NSMutableString *theString = [[NSMutableString alloc] init];

    [theString appendString:@"This"] ;
    [theString appendString:@" is"] ;
    [theString appendString:@" a"] ;
    [theString appendString:@" string"] ;

    NSLog(@"theString is: %@", theString);

}

This method simply creates an NSMutableString object, appends four strings to it, and logs the string to the console. Take a second and see if you can discover the memory leak on your own. The leak occurs because you allocated the NSMutableString object theString using a call to alloc, but you never released this object. After this method ends, the pointer to the string object will be gone. This memory is now unrecoverable, resulting in a memory leak.

Build and run the application. When the simulator comes up, click on the Go button. You should see this text in the console: theString is: This is a string.

Analyzing a Memory Leak in Instruments

Now that you have the sample application coded and ready to go, it's time to find the memory leak in the application using Instruments. In Xcode, select Run

Analyzing a Memory Leak in Instruments

The Leaks instrument is configured to auto-detect leaks in your application every ten seconds. You can force manual detection of leaks at any time by clicking on the Check for Leaks Now button that appears in the left side of the Detail pane when you have the Leaks instrument selected in the Instruments pane. After the application starts and Instruments is running, click the Go button in the iPhone simulator. In a few moments, when the leak check occurs, you will see a red bar in the top part of the Leaks tool in the Track pane and a purple repeating bar below, as you can see in Figure A-4. You have now collected the data that you need, so stop Instruments from collecting data by clicking the Stop button in the top-left corner of the application.

Memory leak in Instruments

Figure A.4. Memory leak in Instruments

The red bar indicates that a leak was found, and in fact, shows the number of leaks. The purple bar shows the total number of bytes leaked. For the sake of clarity, I have stopped Instruments on this run after triggering the memory leak one time. If you repeatedly tap on the Go button in the application, you will see a stair step pattern in the Leaks instrument, as memory is lost each time the button is pressed.

Make sure that you have the Leaks instrument selected by clicking Leaks in the Instruments pane. If you examine the Detail pane, you will see that there are two line items. Each line item in the Detail pane shows you a leaked block of memory. In this case, there were two leaked blocks. The first block is the NSCFString that holds your NSMutableString. The mutable string allocated the second block behind the scenes to hold the new contents of the string after you append data to your mutable string. If you had coded the application to release the NSMutableString, as you will in a moment, you would not see either of these blocks in the Detail pane.

Now, let's see how Instruments can help pin down where the leak has occurred. Make sure that you have the Extended Detail pane open. Then, click on the NSCFString line item in the Detail pane. The Extended Detail pane should show a stack trace that indicates the state of your application at the time that the leak occurred. Notice that the fourth line down in the stack trace is in your code, specifically, in the MemoryLeakerViewController goPressed method. Double-click on the line item in the Extended Detail pane and the source code for the MemoryLeakerViewController goPressed method will replace the leaked blocks in the Detail pane, as you can see in Figure A-5.

Source code in the Detail pane

Figure A.5. Source code in the Detail pane

If you examine the Detail pane, you will see the code fragment that Instruments is reporting as a leak. In this case, the Leaks instrument has flagged the line where you allocated theString as being responsible for 50.0% of the memory leak and the first call to the appendString as responsible for the other 50.0%. Instruments is telling you that you have allocated theString on the indicated line and then the object leaked. It is up to you to draw your own conclusion from there, but using the Leaks instrument has given you the exact location of the origin of the leaked object. Now, you just need to clean it up.

To fix the memory leak, you need to release the NSMutableString object when you are finished with it. So, change the code in the goPressed method as follows:

-(IBAction) goPressed:(id) sender
{

    NSMutableString *theString = [[NSMutableString alloc] init];

    [theString appendString:@"This"] ;
    [theString appendString:@" is"] ;
    [theString appendString:@" a"] ;
    [theString appendString:@" string"] ;

    NSLog(@"theString is: %@", theString);

    [theString release];

}

Now, re-run the application with Instruments. No matter how many times you press the Go button in the application, you should not see a memory leak. Congratulations, you just learned how to use the Instruments tool to track down and fix a memory leak, one of the most difficult types of bugs to locate!

THE STATIC ANALYZER

In the most recent versions of the Xcode tool set, Apple has included the Clang static analyzer. Clang is an open source tool that you can use to analyze your code for problems before you even attempt to execute it. You can do a one-time analysis of your code by selecting Build

THE STATIC ANALYZER

Now, you will see the static analyzer in action. Go back into the MemoryLeaker project and the goPressed method and remove the line that releases the NSMutableString variable theString. You have now reintroduced the memory leak into your application. Select Build and Analyze from the Build menu. You should see an icon in the left-hand margin at the end of the goPressed method. This indicates that the static analyzer has found an issue. On the right margin of the same line, you will notice that the analyzer has noted that there is a potential leak of an object. Click on this notification and you should see something like Figure A-6.

Using the static analyzer

Figure A.6. Using the static analyzer

Sometimes a picture is worth a thousand words and that is definitely the case with the static analyzer. The lines and arrows tell the story. The arrow on line 58 shows that you have created an NSMutableString. The line going from line 58 to the end of the method shows that you have not released the string at the end of the method.

The comments that the static analyzer adds to the right margin explain the problem in detail. The comment associated with line 58 tells you that the alloc method returns an Objective-C object with a +1 retain count. This is also called the owning reference. On line 67, at the end of the method, the analyzer is reporting that the object allocated on line 58 is no longer referenced after this point, yet it still has a retain count of 1. Therefore, you have leaked the object.

Once again, add the line of code to release theString after the NSLog statement. Re-run the analyzer by selecting Build

Using the static analyzer

The static analyzer can find many other problems, too. For instance, it will flag a dead store in which you write to a variable but never read the value back. To see this, change the goPressed method as follows:

-(IBAction) goPressed:(id) sender
{

    NSMutableString *theString = [[NSMutableString alloc] init];
int i=0;
    i=5;

    [theString appendString:@"This"] ;
    [theString appendString:@" is"] ;
    [theString appendString:@" a"] ;
    [theString appendString:@" string"] ;

    NSLog(@"theString is: %@", theString);

    [theString release];

}

You can see that you are declaring the variable i, setting it to 5, and then never reading it again. If you run the static analyzer now, you will get an error indicating that the value stored in i is never read. Dead stores can often be a sign of a logic problem in your application.

Another logic error is using an uninitialized variable as the left-hand operand in an equality operation. Change the goPressed method as follows:

-(IBAction) goPressed:(id) sender
{

    NSMutableString *theString = [[NSMutableString alloc] init];

    int i;

    if (i==5)
    {
        [theString appendString:@"This"] ;
        [theString appendString:@" is"] ;
        [theString appendString:@" a"] ;
        [theString appendString:@" string"] ;
    }
    NSLog(@"theString is: %@", theString);

    [theString release];

}

In this case, you have declared the variable i, but you are using it in an equality test without assigning a value to it. Run the analyzer on this code and you will get a flag on the line where you declared the variable i saying that it has been declared without an initial value. Then, on the next line, the analyzer informs you that the left operand of the == operator is a garbage value.

There are many more problems that you can find even before you run your code using the static analyzer. Although it slows down your build slightly, you should definitely enable the analyzer to run during each build. It will save you many hours of debugging in the end.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset