The Allocations instrument is the memory measurement tool of choice for Objective-C programmers.
This instrument tracks the creation and destruction of Objective-C objects—every single one of them. It’s useful infinding all kinds of problems, but today you’ll be using it simply to measure the total amount of memory your app is using, count the number of image objects allocated,
and observe memory warnings.
Profile your app. When Instruments presents its template picker, choose the Allocations template,
as shown in Figure 23-11.
Figure 23-11. Allocations template
MyStuff starts running and the Allocations instrument begins collecting data. At the top, you’ll see a graph
of all memory allocations. (You can chart any set of allocations you like by checking different items in the
categories.)Begin scrolling through the list of items in the MyStuff app. As you remember from Chapter 5,
tablecell view objects are prepared lazily. As you scroll, the images for each item that’s about to be displayed is lazily loaded from thedocument. Each time this happens, another UIImage object is created.
You can see the effect it has on the overall memory usage, shown in Figure 23-12.
Figure 23-12. Baseline memory use in MyStuff with 1,000 items
If you keep scrolling the table, the memory use goes up and up and up and . . . boom! The app crashes.
You’ll notice a set of flags in the timeline. Click on one, as shown in Figure 23-12,
and it will show you that a Low Memory Warning was sent to your app—several, actually. iOS was trying to warn your app that it was running out of memory. Your app ignored those warnings and kept
loading images, to its peril. You may also see black flags (with an X) in the timeline. These indicate where iOS terminated another running app in order to provide your app with more memory. This underscores how poor memorymanagement impacts other apps, not just yours.
If you sort the list by the amount of memory each type of allocation is occupying, you’ll see that the biggest consumer is VM: ImageIO_PNG_Data allocations, as shown in Figure 23-13.
If you scroll down the list ofobject/allocation types, you’ll also find the UIImage object listed. It’s easier to
findthese objects if you filter the list; the keyword “image” has been used in Figure 23-13 to eliminate many uninteresting categories.
Figure 23-13. UIImage allocations in baseline stress test
In this sample, the app crashed after creating 324 UIImage objects, 305 of which still exist and are occupying memory. So we’ve learned that MyStuff, running on this particular flavor of iPhone, can load about 300 images intomemory at once. But there are 1,000 images in the list! What can you do about that?
Heed the Warnings
It turns out, this problem is also easy to address. Your MyWhatsit object has two image references: the ID of the compressed image data in the document (NSString) and the working image object (UIImage) in memory. Theimage object can be easily recreated, at a moment’s notice, from the data in the document.
If your app is running out of memory, the first thing it should do is discard all of the objects that can be easily reconstructed. Your view controller objects already do this. Every NSViewController object receives a -didReceiveMemoryWarning message when memory starts to run low. (The custom view controller class template includes stubs for these methods, so I know you’ve seen them around.) If the view controller has loaded its view objects, but isn’t being displayed—it had been presented but has since been dismissed—it destroys all of its view objects.1 Its view objects are easily recreated from its Interface Builder file, should it be presented again.
The solution for MyStuff is for every MyWhatsit object to observe these low memory warnings. When one is received, it can discard its UIImage object, knowing that it can reload it from the document using its imageKey property.
Select the MyWhatsit.h file. Add this new method prototype to the @interface section:
- (void)memoryWarning;
Switch to the MyWhatsit.m implementation file and add the new method:
- (void)memoryWarning
{
if (imageKey!=nil && image!=nil) image = nil;
}
The method is simple. If the object has an image (image!=nil) and it has a key it can use to reload that image from the document (imageKey!=nil), then it discards its image object (image=nil). The next request for its image (-image) will create a new image from the document.
Now the question is: who is going to send the MyWhatsit objects this -memoryWarning message? That sounds like a job for the document object. Select the MSThingsDocument.m implementation file. In the private interface section, add a prototype for a new -memoryWarning: method (new code in bold):
@interface MSThingsDocument ()
{
NSFileWrapper *docWrapper; NSMutableArray *things;
}
- (void)whatsitDidChange:(NSNotification*)notification;
- (void)memoryWarning:(NSNotification*)notification;
@end
Locate the +documentAtURL: class method and find the code that registers to observe the
kWhatsitDidChangeNotification notification. Immediately after that, add another one:
[notificationCenter addObserver:document selector:@selector(memoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
Finally, add the new -memoryWarning: notification handler:
- (void)memoryWarning:(NSNotification*)notification
{
[things makeObjectsPerformSelector:@selector(memoryWarning)];
}
The -memoryWarning: method observes the iOS notification that the app is running low on memory. It turns around and sends a -memoryWarning message to each of its MyWhatsit objects, giving each one a chance to releasesome memory.
Stress Test, Round #2
Profile MyStuff again. This time you didn’t save and close the trace document from the last profile. When you work this way, Instruments reuses the open trace document and the data you gather from this run accumulates in thesame document. You’ll see a message like “Run 2 of 2” in the toolbar run status, as shown in Figure 23-14. This is the other technique for comparing multiple measurements
of your app: collect multiple runs in a single trace document, and then flip back through them—using the arrows in the run status or through the Run Browser window in the View menu—to compare
and contrast your progress.
Figure 23-14. Successful stress test of MyStuff
You do the same thing you did last time; when MyStuff starts running, begin scrolling down through the list, forcing the table view to load each item’s image. As you see in Figure 23-14, the memory consumption begins to rise,just as before.
Eventually your app starts to run out of memory and receives a low memory warning (flag in the timeline). This time, it responds to that warning by releasing all of its UIImage objects, dramatically reducing its memory use. Asyou continue to scroll, new rows are drawn, causing it to load
new image objects, and the cycle repeats. What doesn’t repeat is the crash. This time, you can successfully scroll all the way to the end of your 1,000 row table without incident.
If you look at the number of UIImage objects in Figure 23-11, it says that there have been a total of 807 objects created, 747 of those have since been destroyed, and 60 remain in memory. If you continued scrolling, the living count will climb again, until the next memory warning destroys them. MyStuff is now
correctlyresponding to memory warnings.
THE ALLOCATIONS INSTRUMENT
The Allocations instrument is a hidden gem for Objective‑C programmers. You used it in a rather
rudimentary fashion in this chapter, but it can track down all kinds of memory management issues.
The Allocations instrument traces every object allocation and destruction, recording what routine
created it and what routine was responsible for its destruction. In a non‑ARC app, it will also record every -
retain, -release, and-autorelease message sent to an object (and by whom), which can be invaluable in finding retain/release mismatches.The Object Summary lists all of the objects, by class,
that have ever been allocated. You can use the options on the left to filter this to just the objects that
still exist, or only objects that have been destroyed.
If you click the focus button (arrow) just to the right of a class, the list will expand to list every instance of that
object in your app, present and past. Each line records the time the object was created and its address in memory.If your app is stopped in Xcode’s debugger at the same time, you can use the address of an object to
find its history in the Instruments, and vice versa.
The extended detail pane on the right (View ➤ Extended Detail) shows the stack trace when the object
was created. This is the code path in your app that created this object.
The Live column indicates if the object stillexists. This is how you find objects that are leaking—should have been destroyed, but weren’t—possibly due to a circular retain.
Click on the focus button next to a specific instance, and the list shows the complete history of that object.
In an ARC application, this is going to be pretty boring, but can still be used to identify the code responsible for destroying the object. In a non‑ARC app, this trace can include every place where a -retain, -release, and -autorelease message was sent. Working backwards, you can identify code that was supposed to send a -retain that didn’t, or uncover code that wasn’t supposed to send a -release that did.
You’ve addressed two significant performance problems in MyStuff. In the process, you’ve learned a little about Instruments and how to measure code performance and Objective-C memory usage.
But this barely scratches thesurface
of what Instruments can do. I suggest you start by reading the Instruments User Guide, which you can find in
Xcode’s Documentation and API Reference window. This guide is written for both iOS and OS X developers,
so some
topics won’t be relevant.
Summary
Performance analysis and optimization is an important phase of iOS development. Test your app
on as many hardware configurations as possible, to ensure it behaves well for everyone, and under stressful conditions. This is the only way to ensure that your app not only works, but it works well—all of the time.
If you learned nothing else in this chapter, I hope you learned when not to optimize code. Making the best use of your time and talents is just as important as your other development skills. But I’m sure you did learn otherthings. You’ve learned how to launch Instruments to analyze your app’s performance. You can find hot spots in your code, and trace its memory usage. These tools reveal behaviors in your app that you might not have realizedand help you focus your coding efforts in the right direction.
But MyStuff isn’t done yet! I promised you a second way of deferring work, to make the photo picker method even more responsive. That requires some skills you haven’t developed yet. Since you’re at the last chapter, I can’t putit off any longer.







No comments:
Post a Comment