Chapter 2. Tuning Your Code Using Xcode's Instruments Application

In This Chapter

  • Measuring application performance

  • Finding memory leaks

  • Keeping in mind that the Simulator is not the device

  • Hunting down zombies

Yogi Berra, in my humble opinion, is one of the great philosophers of all time. I'm sure he was talking about application development when he said, "It ain't over till it's over." So even if you've successfully compiled and launched your app and it seems to run (correctly), there's still work you need to do.

Before you attempt to get your application into the App Store or even run it on anyone's iPhone, you need to make sure it's behaving properly. By that I mean not only delivering the promised functionality, but also avoiding the unintentional misuse of iPhone resources. Keep in mind that the iPhone, as cool as it may very well be, is nevertheless somewhat resource-constrained when it comes to memory usage and battery life. Such restraints can have a direct effect on what you can (and can't) do in your application. Xcode's Instruments application lets you know how your application uses iPhone resources such as the CPU, memory, network, and so on.

The Instruments application allows you to observe the performance of your application while running it on the IPhone, and to a lesser extent, while running it on the Simulator. Here, instrument means a specialized feature of the Instruments application that zeroes in on a particular aspect of your app's performance (such as memory usage, system load, disk usage, and the like) and measures it. What's really neat, however, is the fact that you can look at these different aspects simultaneously along a timeline and then store data from multiple runs, so you get a picture of how your application's performance changes when you tune it.

Note

The Instruments application is a very powerful piece of software. I have used it quite a bit in my own applications and there is no way, given the number of features Instruments has, and the amount of information it shows you, to include an in-depth discussion within the scope of this book.

I'm going to focus on how the Instruments application can help you deal with two problems you will undoubtedly face:

  • The first is memory leaks. When a block of allocated memory is no longer being referenced from any of the objects in your application, you have a memory leak.

  • The second is the memory leak's evil twin — trying to access a block of memory (sending a message to an object for example) that has been prematurely (at least from your perspective) de-allocated. What makes this a particular challenge is that the stack is often corrupted, meaning you can't even see where you tried to access the object, much less how and why the memory for it was de-allocated in the first place.

After you see how you can use the Instruments application to deal with these two issues, you can explore other capabilities of the Instruments application at your leisure — something I suggest you do before circumstances force you to do so, like the app is due tomorrow and it's still crashing occasionally.

Getting Started with the Instruments Application

To use Instruments, you start with your compiled application — meaning you've already done the Build stuff. Figure 2-1 shows you how to put your application through the Instruments ringer launch by choosing Run

Getting Started with the Instruments Application

Note

When you start your application using Instruments — the performance tool mentioned in the Run menu — your app starts either in the Simulator or in the actual device (if you're running it on an iPhone). Where it starts depends on the kind of SDK you've selected in Xcode. Check out Figure 2-1, paying special attention to the drop-down menu in the upper-left corner of the Project window; Figure 2-1 shows Simulator – 3.1.2 Debug selected. That's fine for now. But when the time comes to test your app on a real device, you'll have to let the Instruments application know that fact by choosing the device from this drop-down menu.

Starting your application via the Instruments application will launch your application, as you can see in Figure 2-2. This stands to reason; your app has to be running in order for there to be data for Instruments to display. Wait for your app to load fully and then keep your eye out for the Instruments window, which should look something like Figure 2-3.

Starting the Instruments application.

Figure 2-1. Starting the Instruments application.

Deep Thoughts is launched.

Figure 2-2. Deep Thoughts is launched.

The Instruments window starts out looking pretty boring.

Figure 2-3. The Instruments window starts out looking pretty boring.

The top pane of the Instruments window shows what particular instruments you're currently running. Figure 2-3 shows that, besides Leaks (which I chose from the menu), the ObjectAlloc instrument is also running. Next to each instrument listed you see a graphical summary of the data that the running instruments generate; each instrument has its own "track." (I guess that's why they call that part of the top pane the Track pane.) You can use the Time Scale slider — located right under the instrument listing in the top pane — to expand the tracks, letting you see the finer details of how each instrument is running. Similar to Interface Builder, the Library button allows you to drag more instruments into the Instruments pane.

The ObjectAlloc instrument — one of the many tools at Instrument's disposal — shows you how your application is using memory. It also shows you a trace of how you create and then release your objects. In a more complex application, it can be very useful to see whether you're actually releasing objects after you're done with them. The Track pane in Figure 2-3 graphs the net amount of memory that your app is currently using. I'll leave it up to you to explore the finer aspects.

Although the ObjectAlloc instrument is very useful, you're not going to find out how to use it in this book. (Hey, I have to prioritize.) Even though it's somewhat interesting to watch what's going on in an application like DeepThoughts (at least one level above watching paint dry), it can be extremely interesting (and also very useful) in more complex applications. In these applications, you can exercise your app and see whether memory is being allocated in the way you expect. You can also find out at what points you're at maximum memory usage, and then you can tune your app if necessary. Here again you should start with an idea of how your application works and the ObjectAlloc traces can confirm or call into question your understanding. You may have been mistaken, or your application may not be behaving in the way you intended. (Ah, yes, your old nemesis the logic error rears its ugly head.) In either case, you now have the information you need to do something about it.

A Leaks instrument preview

When a block of allocated memory is no longer being referenced from any of the objects in your application, you have a memory leak. The Leaks instrument shows you where to go to fix the leak. It works by recording all allocation events in your application and then scanning the application's writable memory, registers, and stack to see whether it can still find references to those particular blocks. If there's no way to either use or free those blocks in your application, the instrument reports them as memory leaks.

The problem is that memory leaks are just sitting there, taking up space until your application terminates. If your application needs to run for a long time or requires a lot of memory, leaks can seriously reduce its performance. That's especially important on the iPhone, which, as I often point out, isn't able to expand memory use beyond physical memory.

Note

Memory is a precious iPhone resource. Too many leaks, and eventually your application may be reduced to a crawl, and it may even crash because it can't get the memory it needs to perform an operation.

Notice that in Figure 2-3, a memory leak does show up. That's the big spike you see in the Leaks track.

Now, let me show you something very interesting. This time I'm going to run the Instruments application on a real device, rather than the Simulator.

I've said it before, and I'll say it again: Running an app on the Simulator is not like running it on the device. The Simulator is not a true simulator; it is an API simulator. This means that even though it's using the same APIs, it still is using your Mac's CPU and memory. It's a marvelous piece of engineering but not a perfect replica of an iPhone, so I want to show you what happens when I test the same application when it's running on an iPhone. In Figure 2-4, you can see that I changed the target SDK to the device (in the upper-left corner), and then I compiled and ran the app and then chose Run

A Leaks instrument preview

If you compare the ObjectAlloc track in Figure 2-5 with the one in Figure 2-3 — where I was running the application in the Simulator — you can see it has changed.

More importantly, notice now that there are no leaks in the Detail pane!

Changing the target.

Figure 2-4. Changing the target.

Look, Ma! No leaks!

Figure 2-5. Look, Ma! No leaks!

Keeping this in mind, feel free to use the Simulator if accuracy just isn't your thing, but in this example I'm sticking with the iPhone.

You're ready to get started, then, with the Leaks instrument.

Actually using the Leaks instrument

To illustrate the Leaks instrument, as well as how to deal with prematurely de-allocated objects (which I'll do next), I've added a new class to DeepThoughts — Zombie.

Zombie is designed to do only two things: leak memory and be the vehicle for the later example of how to use Instruments to track down zombies — what I'll call those de-allocated objects that still stay around in memory.

You can see the inner being of Zombie in the Zombie.m file. Check out Listing 2-1.

Example 2-1. Zombie.m

#import "Zombie.h"

@implementation Zombie

- (void) zombieTester {

  NSLog(@" You've reached a zombie");
  NSString* testString = [[NSString alloc]
          initWithUTF8String: "A never released object"];
}

@end

As you can see in Listing 2-1, I've allocated a string:

NSString* testString = [[NSString alloc]
          initWithUTF8String: "A never released object"];

I never release it, however (how irresponsible of me). So after the zombie Tester method completes, this bit of memory is no longer accessible and becomes (horrors!) a leak.

In viewDidLoad in MainViewController.m, I need to instantiate and send the Zombie object the zombieTester message. You can see that in Listing 2-2. (To create a zombie, I release the object before I send it another message. You can see that in the next section.)

Example 2-2. Instantiating the Zombie and Sending It a Message

- (void)viewDidLoad {

  zombie = [[Zombie alloc]init];
  [zombie zombieTester];

I compile and run the app and then choose Run

Instantiating the Zombie and Sending It a Message

This time, as you can see in Figure 2-6, the leak appears in the Instruments' Track Pane as a noticeable blip. I select Stop; now, take a look at what this little blip tells you.

A real leak this time.

Figure 2-6. A real leak this time.

In Figure 2-7, you can see that I've selected the Leaks instrument in the Instruments Pane, which calls up a detailed view of the Leaks instrument in the lower pane of the Instruments window. In this detailed view, the leaked object is revealed to be a NSCFString object.

When I click the entry showing the culprit, as shown in Figure 2-8, a Follow Link button appears in the Address column.

The leaked object.

Figure 2-7. The leaked object.

The Leak entry.

Figure 2-8. The Leak entry.

Clicking the Follow Link button takes you to an allocation history (a listing of the allocations and de-allocations) for memory blocks at that address. You can see that history in Figure 2-9.

Follow the Follow Link.

Figure 2-9. Follow the Follow Link.

In this view, I'm looking for an allocation (Malloc) without a matching de-allocation (Free) for that block. To refresh your memory, you create a memory leak when you allocate (Malloc) memory and don't de-allocate (Free) that block when there are no longer any objects referencing it.

In Figure 2-10, you can see that tracking down the source of the leak was pretty easy, since there was only one malloc, and no frees. (You should only be that lucky.)

Next, I want to look at all this in an Extended Detail view. To show the Extended Detail view, click the icon my mouse pointer is pointing to in Figure 2-11. (I could also get to this view by choosing View

Follow the Follow Link.
You can see the allocations.

Figure 2-10. You can see the allocations.

Getting to the Extended Detail view.

Figure 2-11. Getting to the Extended Detail view.

When I'm in the Extended Detail view, I can see a stack trace (a trace of the objects and methods that got you to where you are now) in the Extended Detail pane on the right, as shown in Figure 2-12. When I select an allocation event in the Detail pane, the Extended Detail pane displays the stack trace for that event. This stack is organized starting with the oldest call at the top. In Figure 2-12, I've selected the allocation event in the Detail pane that allocated the 48 bytes that were leaked — displaying the stack at that point.

What you also see under General in the Extended Detail pane is a description of the allocation, including the Retain Count, which in this case is, not unsurprisingly, 1.

Check out the Extended Detail pane.

Figure 2-12. Check out the Extended Detail pane.

What I'm going to look for is one of my own application methods. If you right-click in the Extended Detail pane, you can see a contextual menu offering a number of options, including inverting the stack. As you can see in Figure 2-13, that's precisely what I've done.

This moves the offending method (or at least the one that did the allocation) to the top. In this case, you can see the last one of my methods was zombie Tester in the Zombie class — and that was where the memory allocation that resulted in the leak was done.

When I double-click the [Zombie zombieTester] stack entry, I'm shown the problem line of code, as you can see in Figure 2-14.

Inverting the stack in the Expanded Detail pane.

Figure 2-13. Inverting the stack in the Expanded Detail pane.

The offending line of code.

Figure 2-14. The offending line of code.

Very cool.

Yes Virginia, There Are Zombies Amongst Us

When you really get into development and your applications become more complex, you'll begin to run into a bug that can be very difficult and frustrating to track down.

It is in fact the evil twin of the memory leak, and it occurs when you send a message to an already de-allocated object. In the example I'm about to show you, you'll be able to see quite a bit of information in the debugger about the problem — like the offending instruction. Often however, the stack is completely obliterated and all you get is a cryptic message that the program has aborted.

In DeepThoughts I send the message [zombie zombieTester] twice: once in MainViewController.m in viewDidLoad and then later in showInfo, right before I instantiate and initialize a FlipsideViewController. This second time is shown in Listing 2-3.

Example 2-3. A Second zombieTester Message

- (IBAction) showInfo {

  [zombie zombieTester];

  FlipsideViewController *controller =
              [[FlipsideViewController alloc]
              initWithNibName:@"FlipsideView" bundle:nil];
  controller.delegate = self;
  controller.modalTransitionStyle =
                     UIModalTransitionStyleFlipHorizontal;
  [self presentModalViewController:controller
                                            animated:YES];
  [controller release];
}

If I compile and run DeepThoughts and then touch the Info button, what I will see in the Debugger Console is the following:

2010-01-17 08:36:55.594 DeepThoughts[10285:207] You've
   reached a zombie
2010-01-17 08:37:04.411 DeepThoughts[10285:207] You've
   reached a zombie

This makes sense, because I've sent two messages to the Zombie object.

Now, I'll make things a little more interesting. After I send the first zombie Tester message, I'll release the Zombie objects, as you can see (in bold) in Listing 2-4.

Example 2-4. Asking for Trouble

- (void)viewDidLoad {

  zombie = [[Zombie alloc]init];
  [zombie zombieTester];
  [zombie release];

If I compile and run DeepThoughts now, and then touch the Info button, this time I see the following:

2010-01-17 08:41:04.777 DeepThoughts[10309:207] You've
   reached a zombie
2010-01-17 08:41:15.955 DeepThoughts[10309:207] ***
   -[NSCFTimer zombieTester]: unrecognized selector sent to
   instance 0x11fea0
2010-01-17 08:41:15.958 DeepThoughts[10309:207]
   *** Terminating app due to uncaught exception
   'NSInvalidArgumentException', reason: '*** -[NSCFTimer
   zombieTester]: unrecognized selector sent to instance
   0x11fea0'
2010-01-17 08:41:15.961 DeepThoughts[10309:207] Stack: (
    844776241,
    843056877,
    844780055,
    844282517,
    844245696,
    15519,
    844537573,
    851058789,
    851058693,
    851058647,
    851057969,
    851060293,
    851056221,
    851054649,
    851040559,
    851039143,
    848378745,
    844528685,
    844526429,
    848374975,
    848375147,
    850798447,
    850793587,
    10193,
    10088
)
terminate called after throwing an instance of 'NSException'
Program received signal: "SIGABRT".

At first blush, this may not make any sense — I just know that zombie Tester is a method of Zombie. In fact, I just sent it the same message a few seconds before and it worked just fine.

But looking at it in more depth, you can see that it was not Zombie that was complaining,

-[NSCFTimer zombieTester]

It was an NSCFTimer object. What happened was that the memory had been de-allocated and then reallocated to a timer object. When I sent the message to where the Zombie object had been, it was sent to the NSCFTimer instead, because that was the object that was currently at that address.

That was perhaps a bad break — but better than being confronted by some obscure message and an unrecognizable stack.

There's a way to keep zombies around, however, which can be helpful.

NSZombieEnabled is an environment variable — a variable that tells Xcode how to set up the executable's environment before launching it — that tells the runtime to do something special when an object is de-allocated. When this variable is turned on, a de-allocated object is not de-allocated. Instead, its class is changed to NSZombie, and the memory region is not marked as free. Whenever that zombie object gets called, it throws an exception and logs the fact that it was called.

As I have said, on some occasions, the message and the stack trace can hold little information about what happened in your application. NSZombie Enabled does an end run around this unfortunate fact of life by instead logging a message and dying in a predictable fashion, thus stopping the stack right where you need it.

Follow along with me on how to enable it.

  1. In the Groups & Files list, double-click your application under Xcode's Executables heading, as I have in Figure 2-15.

    You should find yourself in the Arguments section of the Executable DeepThoughts Info window. If not, click the Arguments tab at the top.

  2. Click the + tab in the bottom left of the window to add a new variable to the environment.

  3. Fill in the values for the new variable, as shown in Figure 2-16.

    Name the new variable NSZombieEnabled, and set its value to YES.

    Adding a new environmental variable.

    Figure 2-15. Adding a new environmental variable.

    Adding the NSZombie variable with a value of YES.

    Figure 2-16. Adding the NSZombie variable with a value of YES.

Now, when your application crashes, the Run Log will explain what crashed, and the debugger will contain a useful stack.

So, if you were to build and run the application now, you'd see the following:

2010-01-17 08:58:17.748 DeepThoughts[10352:207] You've
   reached a zombie
2010-01-17 08:58:41.520 DeepThoughts[10352:207] *** -[Zombie
   zombieTester]: message sent to deallocated instance
   0x126b40

Tip

You must be careful not to leave NSZombieEnabled in place permanently. Because objects are never truly de-allocated, you'll find yourself using prodigious amounts of memory.

Although this does get me closer to some kind of answer, it doesn't really help me find out what caused the problem in the first place. For that, you need to return to the Instruments application.

In this case, however, I'm going to have you launch Instruments directly. If you notice back in Figure 2-4, the Zombies selection is grayed out when you chose Run

Adding the NSZombie variable with a value of YES.

It turns out that launching Instruments directly gives you access to the Zombies selection. To get that launch going, you need to find the Instruments application, which most likely is hiding in YourHardDriveName/Developer/Applications. If for some reason it isn't there, you can search for it in Spotlight. (It's a Unix Executable file, but don't panic — it has a nice Mac interface on it.)

When you launch Instruments, the first screen you see asks you to Choose a Template for the Trace Document window. Select Zombies, as I have in Figure 2-17 and then click Choose. You then see the old familiar Instruments window.

This time, you'll have to choose the executable you want to trace. In Figure 2-18, I've selected Launch Executable

Adding the NSZombie variable with a value of YES.

I then click the Record button in the toolbar. When the application is running, I touch the Info button, and lo and behold, what you see in Figure 2-19 should bring tears to your eyes — a message balloon telling you that the Zombie sent a message from the grave.

Off to zombie land.

Figure 2-17. Off to zombie land.

Choosing the executable.

Figure 2-18. Choosing the executable.

But wait! There's more! Lurking in the message balloon is a Forward Link that you can click. If you were to do so, you'd see something similar to Figure 2-20, which displays a list of the parties involved — some just innocent bystanders, but also the perpetrators themselves.

A zombie detected.

Figure 2-19. A zombie detected.

Parties to the crime.

Figure 2-20. Parties to the crime.

I'm interested only in my code, so I start at the bottom and select the last method involved. The Ref Count is – 1 here, so this is where the crash occurred and I tried to send the message. (I actually could have gotten this information in the Debugger, but why bother when it's right here.)

I click the Extended Detail View icon in Figure 2-21, and you can see the stack trace that ends at this method — [MainViewController showInfo].

The stack trace at the crash.

Figure 2-21. The stack trace at the crash.

When I double-click on that entry, the listing for that method and where it crashed appears in the Detail pane, as shown in Figure 2-22.

Now, I need to figure out how this mess happened.

I go back to the message balloon and click the Foreword Link again to get me back to the Extended Detail view and the list of methods. This time I select the next to last method, as shown in Figure 2-23. You can see that this is the place the object was released (look in the Event Type column in Figure 2-23) and the place that the reference count was decremented to 0, resulting in the memory being de-allocated. This time, in the stack trace I double-click the [MainViewController viewDidLoad] method that did the release.

Figure 2-24 shows the dirty deed.

[MainView Controller showInfo].

Figure 2-22. [MainView Controller showInfo].

Selecting [MainView ControllerviewDid Load].

Figure 2-23. Selecting [MainView ControllerviewDid Load].

[MainView Controller viewDid Load] is the guilty party.

Figure 2-24. [MainView Controller viewDid Load] is the guilty party.

I can't begin to tell you how valuable this feature in the Instruments application will be to you, especially at 3 a.m. on the day your application is due. Learning about how to use this feature alone is worth the price of this book and something you will thank me for.

There's a lot more in the Instruments application — and you're off to a good start. But I warn you, it will take a while to really get comfortable with Instruments, so start playing with it now.

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

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