Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2009-414
$eventId
ID of event: wwdc2009
$eventContentId
ID of session without event part: 414
$eventShortId
Shortened ID of event: wwdc09
$year
Year of session: 2009
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2009] [Session 414] iPhone Perf...

WWDC09 • Session 414

iPhone Performance Optimization with Instruments

iPhone • 56:22

Instruments is a powerful tool for visualizing what is happening within your iPhone or Mac application. This iPhone-focused session walks you through the collection of valuable runtime data from your application. Drill down using Instruments' latest data mining capabilities to understand memory usage and identify leaks, analyze drawing performance, and relate this information back to your source code.

Speakers: Daniel Delwood, Lynne Salameh

Unlisted on Apple Developer site

Downloads from Apple

SD Video (287.9 MB)

Transcript

This transcript has potential transcription errors. We are working on an improved version.

Howdy. I'm Daniel Delwood. And I'm a Performance Tools Engineer. And I'm here to tell you about iPhone Performance Optimization with Instruments. So first of all, why are we here? Well, performance is so critical on the iPhone. It's such a versatile device, whether you're a seasoned iPhone developer and you have many apps in the app store already, or you're just starting out with iPhone development.

Performance is critical from day one for developing a great app with a little bit more limited resource than a Mac. So today we're going to cover the tools and teach you how to be both proactive from day one in analyzing your application and incorporating this into your workflow, and reactive when problems arise in the field your user support things. How do you use instruments to identify and solve those iPhone app performance problems? So we're going to cover this in really three sections.

First of all, we're going to cover the Simulator versus device. And what are the differences, which do you use each for performance analysis? And we'll go on to talk about instrumentation for common iPhone issues and show you some demos of actually using these instruments. And finally, we're going to talk about reacting to crashes and hangs and other problems that can be caused by performance issues and how will it affect you on the iPhone.

So first of all, the Simulator versus device, what's the difference, and why do you care? Well, the Simulator is an API simulation. It's not instruction for instruction emulation. And this means that it really behaves like an iPhone because you're calling the same APIs, you're allocating the same memory, and the screen is the same size. I mean, the idea is to emulate, or-- sorry, to simulate the experience. However, it has the performance characteristics of your Mac. It has your Mac's cores, your Mac's network card, and even your Mac's graphics card.

And so there are definitely differences there. The run time environment of this Simulator is much like the actual device, in that apps get installed, and they launch and quit in the same way. So when should you use a Simulator? Well, for many of the memory usage patterns in your application, nothing is going to change. You're going to allocate objects, you're going to deallocate them at the same points.

And you can really use the Simulator to great effect for memory analysis because you can just launch from Xcode, make a change, quickly rebuild and go. In the same way, file access is a great characteristic to measure. But like I said, it's definitely too fast. It's much faster than an iPhone. And you get virtually unlimited memory, unlike a small device.

So a take-home point here is, remember that behavior and memory are the same on the Simulator, and go ahead and use it to great, great effect. So what about the iPhone? Well, this is the real performance. This is where you're going to get that accurate timing for your application. It's got the process figure you'll actually be running on and the limited resources. And finally, this is really important because your users are going to experience your app on the iPhone.

And when you ship, you need to know that your performance is going to be up to par, it's going to be that great scrolling experience, that great usage experience. So when you're looking for the speed and responsiveness qualities of your application, definitely, definitely aim at an iPhone. Well, why do you care when it comes to profiling? Well, we have some available instrumentation on the device.

And most of the same that you're used to on the Mac. So, for instance, we have object alloc, the activity monitor, our CPU Sampler and Leaks Instruments all able to the device. However, we even have some that are specific to iPhone, such as the Core Animation Instrument, the I/O Activity, which we're going to go ahead and demo, and the OpenGL ES Instrument for profiling that specific code to iPhone.

In the Simulator, we even have more than the device. You can make use of DTrace and the built-in instruments that come with as well as building your own custom scripts and custom instruments. There's a lot of power there. Also, in the Simulator, you can use the new Time Profiler introduced in Snow Leopard. As well as the new Zombies template, which I'll also demo later on in the talk.

So what does Instruments look like with the SDK? Well, it's the same thing you're used to. It's got the Template Chooser, when you launch it. And if you'll notice on the left, the categories are slightly redesigned from Leopard. And there's the Mac OS X device, there's the iPhone and the iPhone Simulator. So you don't have to remember everything I just told you about what's available where.

You can go right here and click through and see what instruments are available on each device. So without further ado, let's go ahead and jump into a demo right at the top, which is the first thing your user sees, the launch time of your application. So I'd like to invite Lynne Salameh to show us a demo of profiling our app to improve launch times.

Thank you, Daniel. Well, the demo we have for today is called Breadcrumbs. And let me just unlock my phone over here.

And Breadcrumbs is an application that records trails of places you've been to and that are annotated with the latitude and longitude. You can leave these trails of notes at these places you've been to and maybe add a photo to that. So to launch it, I'm just going to click this Breadcrumbs light, and my app comes up. And to add a new breadcrumb, I'll just hit the Plus button over here. And, you know, type in "on stage." You know, I can even add a photo by taking a photo over here, which I'm not going to do right now, and then hit done.

And there I've added my breadcrumb. So if you guys will notice, the launch times for my Breadcrumbs app was pretty snappy on first launch. But when you're running and when you're testing for performance of your target applications, you'd like to run with several user scenarios. And in this case, a more realistic user scenario would be a Breadcrumbs application with breadcrumbs already stored on your Breadcrumbs app. So a whole bunch of photos and notes that you've saved already.

So I'm going to launch Breadcrumbs with a bunch of data. And let's take a look at the launch time. Well, it's still launching. And now it's done. So the launch time over there, you know, took a couple of seconds. It wasn't as snappy as I expected it to be.

And, you know, you can see that I have a bunch of breadcrumbs over here with photos, latitudes and longitudes and some notes. So let me just quit this application and switch back to Instruments. So this is my application in Xcode. And I'm just going to bring it up in Instruments over here.

So as we said, as Daniel just showed you, the template chooser in Snow Leopard has now grouped your templates by category and the column to the left over here. And right now, I'm looking at the iPhone category, and I've selected CPU usage or CPU type templates because I'm interested in seeing my launch times.

And I'm going to click on the CPU Sampler template, hit Choose. Let me just enlarge this for a second. All right. So the CPU sampler template has two instruments; the Sampler instrument and the CPU monitor. And I'm going to go ahead, and in my target chooser, you can see that I've already selected the device as a target. I'm going to launch the executable Breadcrumbs and hit Record.

Now, it's collecting its data. And as you can see, it's already launched my device. So I can stop recording and take a look at the data it's collected. Now, Sampler collects stack traces at regular intervals from the target process. And it aggregates these stack traces and call trees. And right now, I'm looking at these call trees in adverted mode, which means my deepest stack trace is shown first. And then I build my stack traces in reverse order.

So what I'm going to do is, if I zoom in over here-- oh, I can't even zoom in. I haven't set up the zoom. Anyway, over here, I don't know if you guys can see it on the right, I'm going to uncheck Invert Call Tree. And now I'm looking at my stack traces in top, down view. So you can see that I have two threads running my phone, the main thread and then another thread. And then I'm going to bring in my extended detail view by going to the top right corner, hitting Extended Detail.

I'm going to select my main thread. So my extended detail view shows me the heaviest stack traces of each of these threads. And I actually want to time scope my data so that I can look precisely at launch times. So I'm going to drag the inspection head in the track over here and select that time range. And let's bring up the stack trace.

All right, so what's happening over here? My code is calling into RootViewController TableView HeightForRowAtIndex, which is calling into BreadcrumbEntry thumbnail. And that, in turn, calls BreadcrumbStoreThumbnailForBreadcrumb. After I time scoped, and from the stock trace, I can see that I'm spending most of my time in trying to get thumbnails for my breadcrumbs. So the question here that you want to ask yourself is, How many thumbnails am I loading on launch time? Well, let me just remove the time scope.

And from my library icon in the top right, I'm going to add the I/O Activity Instrument by double-clicking on it. And I've just added the I/O Activity Instrument that will tell me about my file opens, closes, reads. And I can figure out how many thumbnails I'm reading in to my app launch time. So I'm just going to hit Record again and start this over. Actually, let's click on the I/O Instruments. There we go. And as you can see, the I/O Instruments is reading in a whole bunch of files. And it's still launching the app.

And I'm getting all of these thumbnails read in. And we're done now. So we can stop the trace. And if you can see the TableView over here, I am opening and reading in a whole bunch of thumbnails. All of these over here are thumbnail JPEGs. And from their labels, I can see that I have 160 JPEGs that I'`m reading in at launch time. Now, this is a very-- I/O is extensive.

And really, we don't need to be loading with thumbnails at launch time. So if I close my library, and once again, you can see the stack traces for where these thumbnails are getting launches, loaded. And the stack trace is, once again, RootViewController TableView HeightForRowAtIndex, which is calling into the code that is reading the thumbnails from disk.

So why am I trying to read in the thumbnails when I am calculating the height of my rows for my TableView? Well, if I go back to Xcode, and here I have the function, heightForRowInIndexPath. It seems when I originally designed this application, I had assumed that my thumbnails are going to have variable heights. So when I am trying to figure out the height of the row in my TableView, I am reading in the thumbnails, I'm calling entry.thumbnail, getting its height, and using that as my content's height. Now, my thumbnails, they're all the default row heights.

So on launch time, when I'm launching, I don't really need to read in all the thumbnails just to calculate the height of my rows. So instead, I am just going to delete this code over here and bring in some new code. That, instead, returns the default row content's height.

Let me just save that. Now, we're not done yet because we still need to read in our thumbnails because we want smooth scrolling. We don't want the user to be scrolling through the rows of our TableView and have the thumbnails getting loaded in a very choppy manner. So we're going to be loading our thumbnails in a background thread that doesn't really interfere with launch time. So we're going to go ahead, and in the Breadcrumbs app delegates. When we're showing the Breadcrumbs content view, I'm going to add in another line of code over here.

And what I'm doing is I am warming the thumbnails on a different thread. So I'm detaching a different thread that warms these thumbnails. And what that means is I am loading in the thumbnails, but I'm doing it on a background thread later on. So I don't really have to load in all my thumbnails at launch time. All right, so let's verify that this does what I expect it to do. Back at Instruments, I am going to launch an app on the phone that already has this fix in.

So select that and hit Record. And if you can see-- well, I can tell because I'm looking at the phone over there. My launch-- I've just completed launch time. But in the TableView, I'm still reading in the thumbnails. So I'm doing that lazily and loading them later. So now I'm at 118.

And now I just finished loading the thumbnails. And if we actually switch back to the phone and take a look at the load time on the phone, you can see it's much shorter than what we originally started with. All right, so now let's take a look at scrolling performance. I have my app, my Breadcrumbs app.

And let's try to scroll through it. It seems pretty choppy. In fact, I'd like to investigate this further, because I bet you can improve the scrolling performance so that it doesn't-- the frame rate is much higher. So let me just quit my app and switch back to Instruments. There we go. I'm going to launch a new trace document by hitting Command-n.

And instead of selecting the CPU category, I'm going to select Graphics. And in the Graphics category, you see a bunch of instruments that relate to graphics on the iPhone. And I'm going to select the Core Animation Instruments because I am interested in the frame rate of my scrolling. And I also would like some debug options to play around with. And once again, I'm going to launch the executable Breadcrumbs And the Core Animation Instruments is reading frame rate over here. And now it's not really doing anything because I'm not playing around with my app.

But let me just walk over here and start scrolling. So the frame rate isn't doing very well. You know, on average, it's about 8 to 6 frames per second. And if I switch back to-- and so if I actually take a look at-- I'm going to stop recording right now. Or no, actually, let's continue recording.

If you take a look at the lower left corner over here, you can see that you have some debug options that-- I can read some out to you. There's color blended layers. There's color immediately or flash updated regions. These debug options are very similar to the options you might have seen in ports debug.

And what I'd really like to do is, I'd like to see whether I have blended layers on my phone, which means that layers aren't opaque, and they're composited on top of each other. And this is a, you know, compositing is a costly operation. And it might be the reason why my scrolling is slow. So I'm going to select Color Blended Layers and switch back to my iPhone.

And what we see here is that opaque layers are shown in green, whereas the blended ones, the ones that aren't opaque and that are composited on top of the background layer, are shown in red. So all my cells that are displaying these labels are composited, which might explain-- which is probably the reason why I have this choppiness in scrolling.

So let's quit this application, switch back to Xcode, and take look at what I'm doing, precisely. So back in Xcode, I'm going to take a look at my BreadcrumbCell view. And this is going to launch up Interface Builder. And I want to verify, I mean, I want to see why my cells aren't opaque. So over here, I'm just going to select Label, which I saw was, you know, being blended. It was displayed in red. And in the options, you can see that opaque isn't checked. So my cell isn't opaque. It's getting composited over the background view.

And that's why I'm exhibiting slowness in scrolling because compositing is an expensive operation. So one way you can change that is, you know, make your view opaque. But another way is to-- because I'm actually, in this case, creating my own custom cells, I could just use the cell API that iPhone 3.0 provides me.

So back in my Root View Controller, if I navigate to CellForRowAtIndexPath, you can see that I'm creating my own BreadcrumbCell instance, which is my style that has the image and subtitle text. So why don't I use the iPhone API instead to use a default cell? So I'm going to bring in a replacement for this method.

I'll drag it in here. So in TableView CellForRowAtIndexPath, instead of creating my own custom cell, I am using a UI TableView cell that in iPhone 3.0 has the option to init with certain style. And I'm going to init, I'm going to instantiate it with a cell style that has subtitles.

And by defaults, the UI TableView cells are opaque. So let me go ahead and delete the old method over here and save it and go back to Instruments. And I'm going to launch the version of the app that has the fix in it on the phone and hit Record. All right, I can-- oh, let me just remove the debug option over here, and go back to my phone and scroll. And my frame rate has improved. And let's just verify that we have no blending anymore.

So I'm going to hit Color Blended Layers again, go back to my iPhone. And now, none of my cells are blended, and my entire-- well, nearly my entire screen is green. So there you have it. All right, so now we've seen how we've used Instruments to, you know, figure out why our launch times is taking too long. And we've also used iInstruments to determine whether we have blending layers or not.

So back to Daniel.

[ Applause ]

Thank you. So let's go through a couple of tools that Lynne just covered and talk about them in a little bit more depth. So first of all, the Sampler instrument. Now, as Lynne said, this takes back traces of your application at a certain time interval. By default, 10 milliseconds. And it gets all of the thread states in your application. And the idea is that it's statistical, not function profiling. So if you have 100 calls to a very, very, very short method, you may not see those.

What you're looking for is the calls to a long method. And we sample it during that long method. And so this allows you to find hotspots in your code that you can go back and really optimize. Now, on the left-hand side were a lot of configuration options for Sampler.

And let's cover those in a little bit more detail. So first of all, sample perspective. Now, there's two ways to look at this. First of all, you can look at it in the all-sample counts way. And this is just what it records by default, showing all thread states and accounts of number of samples you take for those threads.

This allows you to see block threads or running threads. Now, when you switch that to running sample times, that shows you the executing threads, and the executing threads only, and allows you to find out how much time you are spending on CPU. So the difference here really is blocked versus running.

Now, to understand this more fully, let's take an example. So in this example here, the first thing to note is we have 151 samples. At a 10 millisecond interval, that's a second and a half. So we look at the Call Tree, and the most expensive one is mach_msg_trap at 102 samples.

Well, mach_msg_trap is actually sitting there waiting for input and not doing anything. And we'll see that later. Now, looking at our code in Breadcrumbs, we have 26 samples. And these are divided up into 11, 9 and 6. And to really focus in for this, I want to call out BreadcrumbCell init with frame reuse identifier, as well as BreadcrumbEntry thumbnail. And they have really corresponding numbers and samples.

So they take about the same amount of wall clock time, OK? Well, what happens when we switch this to running sample times? Well, first of all, we notice that we're using only 380 milliseconds on the CPU. And second of all, that mach_msg_trap, like I said, it was waiting, and it really took no running time on our CPU. So going back to our code, what about those two frames from earlier? What behavioral difference can we see between these two views? Well, the BreadcrumbCell, init with frame, took 100 milliseconds. It was actually running.

There was some work to be done, allocations, views to set up. But the thumbnail, the BreadcrumbEntry actually gathering the thumbnail wasn't spending its time running. It was actually spending its time drawing in that thumbnail from the database and doing blocking I/O. So that's what the difference in the sample perspective can show you.

Now, as far as call tree inversion, Lynne this off. And that was to identify the costs in the application launch. So, for example, when we click on the button to create a new BreadcrumbEntry, let's say that takes 800 milliseconds, and Breadcrumb's app delegate composed entry. Well, that's broken up into different calls.

For example, the app delegate can show the composed view, and that has to create a nib. And then it has to commit a new breadcrumb, and then it has to make a thumbnail for that entry, which has to do even more work in drawing and even committing the breadcrumb again.

And so what you see is the breakdown of how your time is spent in different calls. Now, some of those, maybe all of them, even, you will control. Or some of them may be beyond your control in APIs that you're calling in to. And so you can really see how expensive they are and maybe learn how to call them less.

And finally, to fill out this Call Tree, we also commit the breadcrumb at the very end when we're done composing the entry. So taking a look at the Call Tree, it looks like that CommitBreadcrumb is taking a lot of time. But we can't tell this just from looking at it at first. That's why we can turn Call Tree Inversion on. And this is for showing the hotspots in the code.

So what it does is, it takes delete frames, moves those to the top of the Call Tree and aggregates the numbers and then generates a new Call Tree from there, going back up the call stack. So this immediately allows you to see that BreadcrumbStore CommitBreadcrumb took 440 milliseconds and is our first place if we want that one spot to optimize.

And we could even find out where it was called from. So the next tool that Lynne showed was the I/O Activity Instrument. And I should mention, this records all the I/O events. Now, this is, you know, the opens, the stats, the closes. And the reason you really need to pay attention to this is because I/O takes a long time. On the iPhone, all of the applications are sand-boxed.

And that means that you can only access files within your application bundle and within your documents and caches folder. So what happens when you try to access files outside of your sand-box? You fail. And the I/O Activity Instrument will show you the return codes for failing I/0 operations and let you track down those calls that you didn't actually mean to make or perhaps you have the paths wrong.

And for those of you who are familiar with Instruments, this is much like the File Activity Instrument for Mac OS X. So also let's talk about drawing. The Core Animation Instrument used is to provide those graphical annotations, the ones that are kind of fun to look at. The blending is to help you out with seeing where your views aren't opaque and where you could actually improve your performance by making them opaque or perhaps decomposing your interface. It will allow you to flash updates so you can see if you're making unnecessary draws.

And even see which images are copied unnecessarily. Finally, it tracks the overall frame rate so that as you make changes, you can go back and forth and see if you are really actually improving performance on things. All right, so let's switch topics a bit and talk about memory. Now, this is absolutely vital on iPhone.

And hopefully you have heard from the other memory sessions we've had how vital it is. But you need to be very effective about management. So we've got a suite of tools for this. And I'd like to introduce them before jumping into a demo so that you understand their purpose and their technology. So first is the ObjectAlloc Instrument.

It's really a heavy lifter. It tracks all of the 'Malloc' type memory. And this includes your Objective-C objects, created with alloc init. It tracks your C calls, created with Malloc, your C++ objects created with New, all of those types. And for Objective-C, it can provide you with retained release histories for those objects as well.

Another benefit of using this is it gives you statistics on how many objects of each type you created, so that you can track whether the numbers line up with what you thought they were supposed to be. And then you can put them in Call Trees and find out what parts of your code were responsible for allocating that memory.

Finally, there's a little bit of overhead with collecting on this data, which is why we recommend doing a good portion of your memory analysis in the Simulator. Set for the next tool, Leaks. This is a very powerful static memory analysis tool. And what it does is it runs through your process looking for unreferenced memory blocks.

These are blocks that you allocated that aren't in use anymore and that are really just causing you troubles because they're upping your resident memory. Now, it works alongside ObjectAlloc to provide you those back traces for Leaks. And what do I mean exactly by an unreferenced memory block, a leak? Well, let's say you have MyObject.

It's just an object with a name and some variable. In your init, you do the standard. I'll set the name to an NSString alloc initWithFormat. And you use it for a while. And in the dealloc, you forget one thing, the name release. Well, this means that when your object goes away and that last reference goes away, you're NSString is still left around with a retain kind of one and isn't deallocated. So your string is leaked. Well, how do we track down the leaks? What we do is we start with all of the Heap Objects in your application.

And then we scan all of your stacks in Global Data. And we scan these for things that look like pointers. So regardless of whether they're actually flags or whatever, if they look like a pointer to a Heap Object, we need to consider them that for being conservative. And I'll talk about that a little bit later. Also scan your data.

And once we find these pointers that reference those Heap Objects, we then follow them, scan the Heap Objects, and then follow the pointers from there. And you get the idea. We scan all of the objects until we can't find any more references. Anything that's left are the leaked blocks. So let's go ahead and see a demo of Leaks and memory growth and how to track it down on iPhone. OK, so I'm going to do most of this analysis in the Simulator.

So I'll just build-- you'll have to excuse me-- and run the Performance tool from my menu and select Leaks. So my app launches in the Simulator. And Instruments starts recording data in the background as it comes up. So what I'm going to do is, I'm just going to try to exercise some of my code. In fact, as much as I can, as many code paths.

And I'll select one of those, select an image, and go back, just really work out my application, OK? In Leaks, what it's doing is, it's periodically scanning my application's heap and checking for those unreferenced memory blocks. And you'll notice in the track view here that the red marks are when I discovered Leaks and the number I discovered. And the blue is just a total overall amount of Leaks memory that I have. So I've collected my data.

I'm pretty happy. I'm going to go ahead and stop. And we're going to take a look at the detail view here. Now, this is a little bit different from Leopard. It's new in Snow Leopard. The aggregation of our leaked objects. Now, what this is doing is, this is finding all of the leaks with the same allocation back trace.

And I can show that by bringing in the extended detail view. And I'll enlarge my window a bit. And so they're all allocated at the same point. And we can even turn this down and see that there's a whole bunch of NSStrings with different addresses. So each of these are leaked. Now, I could just look at the allocation stack trace.

But from tracking down most leaks, it's important to look at the whole retained release history for the object. It's possible that it wasn't leaked at the point of its creation, but later on, because someone over-retained it. So there I selected the Focus button next to the address of one of the leaks.

And I'm presented with the full retain release event lifecycle for that string. And so you can see at the beginning here, we've got a Malloc with a rough count of one. See if retain increments the rough count. Auto release has no effect until the release pool is popped later on. And you'll notice that the CFString has a final reference count of one when it's leaked, which means that we've probably missed a release somewhere, or have an extra retain.

So how do we track this down? Well, it's usually true that near the beginning and near the end of the lifecycle are the points that you want to look for errors. And the lifecycle management rules for Cocoa are pretty simple and straightforward. But they're easy to get wrong. And they're easy to just make that little mistake. Sort of like missing a semicolon.

So what I'm going to do is I'm going to select the Malloc point and go to the extended detail view and just double click on MyFrame. Well, in Snow Leopard, this brings it up right in Instruments. I can take a look at my source code. And you'll notice that the string that was leaked was the subtitle text. So it was alloced initWithFormat.

And so now my subtitle text has a retain count of one. And then I assign it to cell.detailtextlabel.txt. So hmm, that's interesting. Let's go ahead and jump into Xcode with the button in the top right here. And I'm going to Command double-click on .txt here to take a look at the property declaration for UI label.

So this text label here is declared as non-atomic copy, which means that it will copy the string and take ownership of it. That's great. But what am I left with? I'm left with a subtitle text which I have a retain count on and I need to release it. And so the simple fix, just like I forgot a semicolon, is just to put in subtitle text release here. So I save, I build, and now I am going to go ahead and run this in Instruments.

So I've got to run the Performance tools, Leaks, and my app starts up. And I'll go back to the main view here and exercise my application again and see if the leak reproduces. So I put it through its paces. And our result, the NSString leaks are gone. So now we have a couple other leaks that we need to track down But it's an innovative process. You should make a fix, see how the improvement works, and go from there.

So all right, that was Leaks. And those were on reference memory blocks. But we need to actually be a little bit more proactive about tracking down our memory usage. What about memory that we still reference, but we don't still need? Well, for this, what I am going to do is I am going to bring up, under the Memory section, just Object Allocations. Now, again, this lets me track all of my Malloc memory.

And if I select my application in the Simulator and hit Launch. By default, this graph is going to show me, and I'll enlarge it a bit, this graph is going to show me the amount of memory that I currently have allocated. And that sudden spike after a couple seconds was that delayed thumbnail loading that Lynne mentioned. So now my app is up and running and I can scroll around.

And there isn't much memory growth. When I select an item, you can see live in the Instruments trace that there was a little bit of memory growth from loading all of this text. And when I select my image, there's quite a bit of memory growth. And then I go back to my main view. But that image doesn't get released. I was hoping it would. In fact, I really don't need that image around, because I've got the thumbnail. So I'll go ahead and stop that And I'm going to take a look at why that image didn't go away like I expected.

So if I time scope to this area and type in my category here, Image, you can take a look at the image types I have. And so I have a UI image here, I have two images I have created in that time period, both that are living at the end of the time period, and I'll go ahead and select the category to see which ones.

And so if I bring in the extended detail view, this first image was created when I clicked the photo. And it was calling the image method of the BreadcrumbEntry, which was then calling into my Breadcrumb store, which is a database, and asking it to load an image with the contents of file.

OK, great, that's probably the image I'm looking for. So why don't I jump directly to this code and see what's going on? So here I go. The image is initWithContentsOfFile, the image file name for Row ID. Great. But where does this get returned to? Well, I'll just use the extended detail view to jump directly there. So the image is getting stored in an ivar named m_image. And that's great. And that's being stored in the BreadcrumbEntry.

Well, why is that ivar not going away? Well, if we go back to Xcode, you'll notice that I have to go to the point at which I expected the image to go away. And that was when my composed view controller left. That was when it disappeared. So when the view disappears, I asked if I needed to save, and I saved it out to the database.

But then my entry is still around. That entry object is still holding onto the object. And you can see in the definition for entry that it has a bunch of properties. Most of these would load from the database, including m_image. So what I'm going to do, let's go back to that point, and I'm going to ask it to dehydrate. And I will show you what this method does in a second. Let me go ahead and load this code.

So I'm going to ask that BreadcrumbEntry to dehydrate. And what this does is this is just going to release most of the resources that I can load from the database because I don't need them. I'm just showing the brief summary description. And I can load the thumbnail at will. And so by getting rid of these resources, I reduce my active memory footprint. And the other thing I need to do here is add this dehydrate call to viewedItUnload because viewedItUnload is what's going to be called during memory warning.

And I want this also to happen then. So I build. I'm going to run with Performance tool, Object Allocations. App goes ahead and starts up. And after a few seconds, we should see that all of the thumbnails getting loaded. And I scroll around. And now I'm going to see if my image gets unloaded promptly.

So there's the image being loaded. And as I leave the composed view, it's immediately unloaded. So just to watch that again, it's really quick and responsive in getting rid of my resources that I don't need. So again, be proactive. Make sure that the behavior is exactly what you expect and what you hope for in your memory usage.

OK, so, I mentioned earlier that Leaks was conservative. And what I mean by this is that it works very, very hard to avoid false positives. This means that when it reports a leak to you, you can believe that it's leaked, and you need to really investigate what's causing that leak. Now, this also means that it doesn't catch everything because you need to make sure.

And if you're leaking just one object, well, you should probably test out your application a bit more so that you leak 5 or 10 objects. And you'll catch most of those with Leaks. It's a very, very powerful tool, and it also should be used alongside other tools, such as the Xcode Static Analyzer.

So as far as memory, growth over time is something you need to very actively avoid. And this is what we showed in the demo of that image memory not being released immediately. So be conscious of your memory usage. Be actively investigating those problems. And prepare for the memory warning case so that you can release those caches and get rid of that memory very quickly. So for more information on the memory management guidelines for retain release, definitely go to the Memory Management Programming Guide. I would bookmark this, post this on your wall.

It's a great guide and something that you really need to make sure you understand fully. So let's go and talk about one more memory issue. It's very iPhone-specific. And that's cell reuse. Now, this is something that's a little bit different. Because as you use cells, normally you would create them, they get used. As they go off-screen, they just get destroyed. And so this has you creating a lot of objects and then deallocating them.

Well, there's a bit of a cost involved with both the allocation of the object and the allocation of the layers in the setting up of the view hierarchy. And so what cell reuse in iPhone is about is taking those cells as they go off-screen and taking them back around to the bottom and reusing them to avoid the costs and penalties of allocating these objects. So let's go ahead and take a look at making sure our application is implementing cell reuse properly.

OK, so again, from Instruments, I'm going to start with the Object Allocations template in the Memory section. And I'm going to launch my Breadcrumbs application. So Breadcrumbs launches. And for this, I want to view something different than just the overall bytes I'm using. I want to see how many objects I'm creating and deallocating. So if I hit the Inspector button, I can change my track display style from current bytes to things like allocation density and stack depth.

And allocation density is what I want for this because that's going to show me the number of allocations I make in a given time period. So if I mouse over, I can see that early on, I did about, oh, 97 allocations in a millisecond. So I'll go ahead and watch this as I scroll. And you'll notice live that it's doing a bit of allocation. So interesting. I wonder if that's correct.

So to find out if I'm using the cells correctly, I'll go ahead and go on the bottom right and type in "cell." So what I have here are my UI TableView cells. And the numbers show me that I have 6 living cells, 6 cells that are still in use, and 150 poor cells that I've created and destroyed. Well, as I scroll, I'll go back up, the number of living cells goes between 7 and 6. But the number that I created and destroyed is just going up without bound.

In fact, it's at about 275 now. So I'm probably doing something wrong. Now, to watch the cells in particular, I can just select the Focus button on UI TableView cell. And it presents me with all of the TableView cells in my application. Now, you'll notice for this that the live column is important. Because if I scroll to the very bottom, all of the live cells are still marked with a bullet. So I'm interested in the ones that were deallocated.

And if I bring in the extended detail, I'll notice that this is in my TableView CellForRowAtIndexPath. So all right, I'll go to that code. And it's a little bit big text. So I'll go ahead and show it here. And here is my TableView CellForRowAtIndexPath implementation. So I'm using my initWithStyle method. But most importantly, every single time I'm coming through here, I'm calling alloc init. So this reuse identifier is not being used. And again, every single time I come through, it's allocating the cell.

So how do I fix this? Well, it's pretty easy. What I'm going to do is I'm going to define a cell identifier. In this case, it's just cell. And the first thing I need to do before allocating anything is ask my TableView, Hey, is there any reasonable cell with that identifier around? If there is, then I can just go ahead and use it.

I'll remove these two lines. So if there isn't any cell, then I need to go ahead and create one. And I'll create it with that reuse identifier that I just declared above. Very simple, very straightforward. Let's make sure it works. So I'll go ahead and build. Go back to Instruments. And I will stop and rerun.

So I'll go ahead and go back to the main TableView to we can watch this live. And again, we have 6 living cells from the beginning. So it starts growing, the number goes up to 7, and that's it, just 7. There aren't any more transitory objects being created and destroyed. And you'll also notice that the number of allocations is actually significantly lower, as we scroll. So again, another example of being proactive and making sure that the behavior is what you would expect when it comes to memory.

So finally, it's good to be proactive. But sometimes things just go wrong. So what happens when you find performance problems and how do you deal with them? Well, you need to be responsive with your application. And if your app isn't, users are really going to notice. And then they're going to hit the Home button, and they're going to wish your application quit. And when your app doesn't, because it's saving its state and taking a long time, your app is going to get terminated.

To preserve that good user experience on the iPhone. Well, this means that you need to work very, very hard to not block the main thread. What do I mean by that? I mean deadlocks, I mean network activity, synchronous network activity on the main thread, infinite loops in your code, anything that causes the main thread to stop being responsive.

Now, you can use Sampler to track this down. And I would highly suggest that you profile the device because, again, that's where your application is under the most constraint and the users will experience your app's responsiveness. Well, what about memory? Why do you need to use that wisely? Well, the system has to protect itself if it's about to run out of memory. And that will result in your app getting terminated again.

So what do you need to do? Definitely fix leaks. Definitely work very proactively to reduce your memory growth over time. And then you need to respond to those memory warnings. There was an earlier session about that. And you can come find us in the labs if you need to talk about that more. But there are three simple places to respond.

And it's very, very necessary that you do this and you plan ahead. And finally, the Simulator provides a lot of convenience when dealing with memory, in both the fact that you can analyze the same behavior, you can integrate it into your workflow even faster, and you can simulate those memory warnings, just to make sure you're responding properly. Well, this could also happen, too. And when it crashes, what do you do? Well, you need to take a look at the crash trace.

And the one I really want to call out here is this one. Because you crashed apparently in Objective-C Message Send. Now, Objective-C Message Send isn't at fault. What's really happening here is that you sent a message to a deallocated object. So as we've been harping a little bit on, over-release objects are quite a problem.

And while leaks are over-retains, over-releases lead to crashes. So this happens. You create a string with Alloc initWithFormat. It has a retain count of one. You go on to release it. Retain count goes to zero. And then the object gets deallocated. It's just a block of freed memory.

Well, when you try to send a message, that's what's going to get you your crash in Objective-C Message Send. Where a nice new Zombies template is going to change the behavior there. And that last release will decrement the retain count. And instead of freeing the object, it turns it into a Zombie and leaves it in your process's address space for the rest of its execution.

Now, when you send that extra message, then the Zombie responds. And we can track its response in Instruments and present that event to you. So anyway, let's go ahead and see a demo of just using that and tracking down of releases, which has historically been a hard thing to do. OK, so as I was developing my application, I found that there was a point at which I was crashing pretty hard. And here is my application.

I'm going to go ahead and build and debug it. The app comes up. And it was scrolling fine and it was displaying these fine, and it was displaying the images fine. But when I backed out to the main TableView, I was getting this crash. And you'll notice it was in Objective-C Message Send. And it even corrupted the stack. So this is a bit hard to track down. So let's go ahead and see how we can use the template to identify this over-release. So go into Instruments.

Under the Simulator memory area, also in Zombies, and you'll notice this is again the ObjectAlloc instrument we're working with for a long time. But it's got the record reference counts option on in its inspector as well as the enables in a Zombie detection. Now, again, this will alter the behavior of every last release. So be aware of that.

So I launch my application in the Simulator and try to reproduce my crash. So nice picture. Oh, there it goes. So immediately Instruments tells me that I messaged to Zombie saying an Objective-C message was sent to a deallocated object. It gives me the address and it gives me a Focus button. So I'll use it and jump directly there.

And my detail view here shows me some information that I immediately wanted to know, such as the fact that it's a UI image, and the retain release history for it. So I've got a Malloc, an auto release, a couple retains, releases, and that last message was sent to the Zombie. So my goal here is to determine which release was extraneous.

And I'll use the guess that it was probably in my code. So here is my responsible library column. And this makes a guess at finding out which library, which binary was responsible for making these reference counting calls. It's a good first estimate. So I'll look through here and look for any releases that are in Breadcrumbs. And here we go. And I'll go ahead and double-click to jump there. And you'll see that it's in my viewDidLoad.

And so I've got my m_imageview.image equals m.image, which is setting the image property on the image view. So that view will retain the object, which is why it's fine for me to release my ivar, OK? So I'll go back. The second one is this release here and also on Breadcrumbs. That's where I double-click. We're in the dealloc now.

And you'll notice that I am releasing my M image. Well, when I go back to Xcode here, you see these right beside each other. Neither of these is wrong, but both of them is. And which one is right? Well, just to make this simple, I'm going to go with the standard pattern of retaining my image in the init and releasing it in the dealloc.

In my viewDidLoad, I am not going to release this. So I'm going with the standard pattern there. I've removed that extraneous release. I build. And I'll go ahead and debug it again and see if it crashes. So here we go. Select an image and go back to the main view. It hasn't crashed yet. One more. And we're good.

So there we go, tracking down an over-release. So in summary, I want to encourage you to use Instruments for the iPhone. It's got a lot of powerful tools, a lot of great instrumentation. And it's really important for you to be proactive in tracking down the problems that you may face. Know the difference between the Simulator and the device. When do you use the Simulator? Mainly for behavior and memory analysis.

And when do you use the device? For that really speed and responsiveness and the final test before you submit it to the app store. And again, I can't stress this enough, but be proactive. Don't wait until you get crash reports to start up Instruments and to profile your application. Now, for more information, you can talk to Michael Jurewitz. And there's his e-mail. And we've got some Instruments documentation in the user guide in Xcode. 1