2009 • 1:10:11
To create an optimal iPhone experience for your customers, your app needs to respond instantly to user input, start up quickly, and use power efficiently. Learn the techniques that optimize CPU usage to minimize power drain, make efficient use of available memory, and give your table views the smooth scrolling users expect. Discover this and a wealth of other best practices for making your iPhone applications perform at their best.
Speaker: Michael Jurewitz
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Hi, I'm Michael Jurewitz, the Application Frameworks and Developer Tools Evangelist at Apple, and welcome to Maximizing iPhone App Performance. Today, we're going to focus on helping your application perform its best, and we're going to take a look at everything from drawing and scrolling performance to minimizing power consumption and optimizing memory usage in your application. Now, whether you're an application or game developer, you'll find something to help improve your development. So let's take a look.
So today we're going to talk about a whole bunch of topics. We're going to talk about drawing and scrolling, application launch, memory usage, files and data, and power and battery life. So to properly do performance analysis on the iPhone, it's important to actually be familiar with the tools in the first place. So let's take a look at those.
[Transcript missing]
Now when it comes to actually choosing the right environment, there's a couple things to be aware of. The first is that the simulator is an entirely valid place for you to do quite a bit of performance work. Really anything that involves looking at memory, for example, or the overall activity on the device.
So whether you want to look at leaks or object aleck, you can do that all in the simulator. And in fact, it's a lot more efficient to do so for speed reasons. When you use something like object aleck, we're having to record every single object event that happens in your application. That can take a decent amount of time. And so doing that in the simulator is nice because then you're operating on the fast Intel processor on your desktop or laptop.
Now, obviously, you can also use the device, and for the wide range of other types of profiling, this is what you want to use. So anytime you're looking at graphics, or the precise timing of your application, or when you want to use Shark, you want to be doing this on the device. So again, just make sure you're choosing the right tuning environment. Alright, so let's hop right in. It's time to take a look at drawing and scrolling.
So now the various drawing technologies on iPhone OS are pretty diverse. You've got Quartz, Core Animation, OpenGL, built-in UIKit drawing, really just a lot of options to choose from. And you want to make sure that you use the built-in drawing whenever possible. Now, UIKit views are usually very optimized at accomplishing their job. So UI Image View, UI Label, they both know how to draw to screen very efficiently. So please use them whenever possible.
Michael Jurewitz Now, when you want to take advantage of things like animation, you should also look at the built-in controls, because in many cases, they handle animation for you as you begin to change the different properties on them. So try to prefer using those as opposed to immediately rolling your own hand animations.
When it comes to drawing optimizations, and once you get down to custom drawing, you need to make sure that you follow a couple key steps. The first is to make sure you mark your views as opaque. This is absolutely critical. Now when you create a view by default, it comes back as opaque, but there are some situations where you might be tempted to set it to be translucent. You don't want that in most cases. When you make a view translucent, there's a lot of complicated compositing work that has to be done. The second step is to make sure you draw minimally.
This means calling set needs display in rect and passing in just the rectangle in your view that is dirty and needs to be redrawn. Now when you do that, you also need to make sure that in draw rect you actually check the rectangle that gets passed in, so that you do only draw the section that's in the rectangle. Obviously, it doesn't do much good if you pass in the right rectangle into set needs display in rect, and then in draw rect you just redraw everything.
When it comes to custom drawing, again, you want to make sure that you cache static drawing objects, things like images, for example. You don't want to be constantly loading these things from disk, but again, once you cache them, you do still need to be prepared to respond to low memory warnings, so do be prepared to release these.
A really common bug that we see with drawing involves timers. So when the user is using your application and you're using a timer to draw to the screen, and something like a text message comes in or a push notification, your app gets suspended. A lot of times people forget to invalidate the timer that's doing that drawing.
And when they come back from being suspended, they create a second timer. And now they're drawing 60 times a second or 120 times a second or more, depending on how many times they got interrupted. That'll burn through your battery really fast. So do make sure that you invalidate those old timers before you create new ones.
You also want to make sure that you use ping files for any of your image assets. Now we take these ping files and we optimize them at build time. In this case, we convert them from an unmultiplied RGBA to a pre-multiplied BGRA. If you're not sure what that means, basically all that we're doing is taking the image and converting it to a format that is most easily displayed fast and efficiently by iPhone's hardware. Now along the way, we'll also remove some unnecessary data.
Michael Jurewitz And the compression rate may actually increase, but in general, you'll still find that your images look good. Next, let's look at scrolling performance. Now scrolling performance is one area where we see people tend to have a lot of trouble, but there are some really simple rules that you can follow to help increase this performance. So first and foremost, use opaque views. Again, anytime that you don't use an opaque view, we're going to have to do a bunch of expensive compositing. That takes time, but it's a lot of work.
Michael Jurewitz And the reason why you want to use opaque views is because it's a lot of time, it slows down your scrolling, and really it's pretty easy to avoid. So make sure your views are marked as opaque. You also want to make sure that you avoid allocating views while you're scrolling. Now when you think about it, allocation in general can be very, very expensive.
In sort of the worst case, you actually have to go down to the kernel and wait for it to do a bunch of bookkeeping to actually give you back the region of memory for you to work with. If you're doing this time and time and time again while you're trying to scroll in new images, you're going to have to do a bunch of bookkeeping to actually give you back the region of memory for you to work with. Michael Jurewitz And if you're dealing with a movie cell that looks something like this, in this case we've got a table view cell, we're showing a movie poster, we've got a title, a description, who actually was in the movie, a rating image.
Well, there's a lot of different views that are made up here. You've got an image view for the poster, you've got a UI label for the text for the title of the movie, another UI label for who was actually in it, another UI label for the title of the movie, and then you've got a table view cell that looks something like this.
Michael Jurewitz And then you've got a table view cell for the description, and then another UI image view for the actual movie rating image. So already in that relatively simple movie summary cell, we've got a ton of different stuff going on. So the way to avoid having all that allocation is to reuse your table cells. You do that by tagging your table cells with a reuse identifier.
And then when you actually go into cell for row at index path and you're actually trying to do that, you're going to have to do a lot of different things. Michael Jurewitz And then when you actually go into cell for row at index path and you're actually trying to do that, you're going to have to do a lot of different things. So now let's look at how scrolling usually works if you don't reuse your cells.
So you've got a table view and you go to scroll in some content, it comes in from the bottom, it goes through the top, and ends up going off screen. And then the cells go away, they get deallocated, the memory gets reclaimed by the system. And then you have to reallocate to get new views on screen. Now once you go and actually add in the use of this reuse identifier, it looks something like this.
You've got a bunch of cells. You scroll them in. They go on screen. They go off screen. And then when you have new content that needs to come back on screen, we take those existing cells, bring them right back around, and feed them right back into the interface.
Michael Jurewitz So basically, we're just keeping a ring buffer of cells and reusing these things over and over. This helps you avoid allocating all those views and lets you basically just stuff in the new information to display. Now, this will take you a long way, but to get the best scrolling performance possible, you'll need to collapse your cell view hierarchy. Now, what does that mean exactly? Well, we've got our cell here, in this case, this movie summary cell. Michael Jurewitz And we've got all these individual views.
Michael Jurewitz And we've got all these individual views that we're actually displaying in the cell. Michael Jurewitz If we wanted to collapse the cell view hierarchy, we would end up with a cell that looks something more like this, Michael Jurewitz where we just had a single view, in this case, this movie view, that knows how to draw all of its contents. Michael Jurewitz By moving to this model, we're able to just have one view do the drawing and just composite all that information once, as opposed to having to ask each and every individual view to do its drawing, Michael Jurewitz and then stitch that all together.
Now, if it's not clear what's happened here, conceptually we've gone from our table view cell with its content view, and that content view of course has pointers to all the different sub views that it's displaying. So we've gone from this to just a single view, this movie view. So let's go ahead and hop into a demo here. And I'm going to start off with an application that doesn't scroll too well and show you how you can help it scroll faster.
So let's take a look at a really simple demo application. In this case here, we're just going to take a look at an application that shows a list of movies. So I'm just going to select Build and Run, and let's take a look at this on the iPhone.
Okay, our application is launched and we've got our user interface here. Now we can go scrolling through the data that's here, and we see that this scrolls fairly well, but it's a little clunky in places. We see a couple little jerks as we go scrolling through this content.
So we can actually take this behavior and make it even better. So let's go back to instruments and try to figure out what's going on. Michael Jurewitz So we're going to take our application that we've already built, go to the Run menu, and select Run with Performance Tool. Now we're going to take a look at object allocations.
This will go ahead and start up instruments, and in this case it's going to start the application on our iPhone again, and we're going to get a chance to see our memory usage live on the device. So now if I go to the iPhone and just start scrolling through the content, things are going to be a little bit slower, but in this case it's because we're recording a lot of information from the iPhone itself, so this is okay. What we want to pay attention to is what's actually happening to our graph of objects in instruments. We see that this graph is slowly growing over time. That's not the kind of thing that you want to see as you're just scrolling through a static list of content.
So I'm going to go ahead and stop this and see if we can figure out what's going on. Now, as you begin to tackle these kind of problems, you want to think to yourself, well, self, what sort of classes am I working with at this point? Well, obviously, we've got a table view. It's got table view cells. So let's go ahead and search for cell.
Now we see all the different allocations that we have for this movie summary cell, which is the cell subclass that we're using in our application. In this case, you'll notice that we have 127 objects that are still living and 127 objects overall that we've allocated. This is not the kind of thing that you want to see, especially when your table view only has 20 or 30 items in it. This is our first indication that there's probably a leak that we need to take care of. So let's go ahead and take a look at this movie summary cell and see if we can figure out what's going on.
I'm going to click this arrow right here and this will jump to all of the individual allocations that I've made of this movie summary cell. And you'll notice that all along the way I see which ones are still alive with this black dot. Now I'm just going to select any one of these things because they all look to be the same. And if I click the button for the extended detail view down here, I'll get a chance to see a back trace for each one of these allocations.
And this is going to be the same as the back trace for the movie summary cell. In this case, I can see exactly where this allocation came from. And we see this line in my code right here, this movies view controller table view cell for row at index path. That makes sense. So let's double click that.
And in instruments, we actually get a chance to see the actual allocation that took place. We see that I've allocated and knitted with a style. In this case, I've just passed in the default style. I'm not using a reuse identifier. And moreover, I've not actually releasing this cell. So let's see here. Do we ever release it? Nope.
Well, that's a problem right there. The table view expects to be receiving an auto-release object. And since we haven't auto-released it, this is getting leaked as we go scrolling through our application. So let's click the Xcode button here to jump to Xcode. We jump directly to the source file that we want to work with.
And I'm going to go ahead and just add auto-release to this method. And I'm going to Thank you. We see Xcode adds the bracket for me. I'll hit save, and now I'm just going to build my application. Go into the build menu and selecting build. We see it succeeds. And now I'm going to go back to Run with Performance tool and go to Object Allocations.
We'll reinstall the application onto our iPhone and we'll launch back into Instruments. All right, we see that we've been recording some data here. We've got some information about our application. And now what I'm going to do is go ahead and scroll through this data again and let's see what happens in Instruments.
So I go scrolling through. Again, things are kind of slow because we're collecting a bunch of information about these individual allocation events. And as I look back at instruments, I can see that the graph is flat over time. This is exactly what you want to see. If I hide this extended detail view, you notice that we've allocated 60 cells overall, which is less efficient than we could be, but we only have six that are still living, which is exactly what makes sense based on about how much content we're able to fit on the screen.
Okay, so we've tracked down this leak and we've fixed that. But we're still not reusing our table view cells. So let's go back to that same code and make that fix. So I'm going to go back to my Xcode project. And in this case, I want to change this reuse identifier. So I'm going to go ahead and just insert some new code here and get rid of the old code that we previously had.
So we see now we're declaring an NSString with this movie summary identifier, and we simply pass this in to DQ reusable cell with identifier. Now if there are any cells to reuse, we'll go ahead and have one handed back to us. If we get back nil, it's our indication that we need to go ahead and create a new movie summary cell, which we do here.
After that, we go ahead and set the new content into our particular cell. Here we're just updating the information that it's going to display, and then we return that to the table view. So now I'm going to go ahead and build this project again. And then go to the Run menu and again select Run with Performance Tool and take a look at Object Allocations. And we'll go ahead and install this application on our iPhone.
We launch into instruments and immediately begin collecting data about our application. We can see our movie summary cell is now showing up. And as I begin to scroll through my application, we'll see that the number of overall cells that have been created is seven, and the number living is also seven.
That's exactly what you want to see with table view cell reuse. As you go scrolling through your application, if you're properly reusing your table view cells, they should stay pretty much stationary right around a reasonable number of cells to be displayed on screen. So that's great. We fixed that bug.
Now if I go ahead and just launch this application one more time on the phone, you'll get a chance to see some of the difference that this actually made. So we'll go back to the iPhone, launch our application. And as I go scrolling through here, you'll notice this is already scrolling much faster than it was before. It's much more responsive with my finger, and you see we can go through a lot of content without any skips.
Now as it turns out, we can actually do even better than this. So let's go back to instruments. The next thing we want to look at in our application is make sure that we're not doing any unnecessary compositing. So I'm going to go ahead and close this document window in instruments.
Now we're going to bring up a new one. And in this case, I'm going to go to the Core Animation instrument. Now the Core Animation instrument lets you see lots of really detailed information about how you're doing your drawing. You'll see we have the instrument selected up here. And if I go and bring in the individual information on this instrument, there's a bunch of different options for me to choose from.
The one that we're going to be using primarily is Color Blended Layers. Color Blended Layers is going to change the color of any user interface. It's going to change the color of any user interface element in our actual interface that happens to be getting composited. Now some other really useful ones to use are things like Flash Updated Regions. This is going to flash a region of the screen yellow any time you update that content, usually through redrawing. This is a great way to check that rectangle that you pass into DrawRect and make sure that you're only drawing the minimal amount.
So now what I'm going to do is go ahead and click this color blended layers. And I'm going to do this with my application already active on the phone. So let's take a look at the phone first. So I've got my application up on the phone, and I'm going to go ahead and click color blended layers and instruments, and boom, you see the color changes in my interface immediately. And I get a chance to see all the different places where I'm compositing.
Now the key here is that green means good and red means bad. So all of these different places where we've got text, you can see we're compositing all this information, and that's slower than it could be. So let's go back to our code and see if we can figure out why this is the case. So we'll go back to Xcode.
And in this case, I'm lucky enough to have been the author of this project, so I know a little bit about how this actually works. So I'm going to go ahead and go into my movie summary cell. And I've got a method here for creating a new label. And we see, sure enough, here I set clear color as the background color for the label.
Now, if you're clever, you've probably thought to yourself, well, wait a second. If I've got a table view, of course I want the background on the label to be clear. Because when a user taps on a row, I want it to properly highlight. Well, yes, you're right. You do want that. But as it turns out, the UIKit engineers have already thought about this for you. And so all you need to do is actually specify whatever color you want as the background for your cell to avoid compositing. Say something like white color.
And when the user actually selects the row, we will go through the view hierarchy and properly take that label and turn its background to clear so the background gradient can shine through. And if the user deselects that row, we take care of setting it back to what it was. So again, the proper thing to do in this case is to take out this clear color and go ahead and use white color instead.
I'll go ahead and save and hit build and run to redeploy the application to my phone. And with the application launched, we can see we've got a nice green interface. This is what you want to see, just the sea of green. This is your way of knowing that you're not doing any unnecessary compositing. All right, so that's great. Let's go back to Xcode. So it turns out you can actually get one step better than any of this. So let's look at a different version of our project.
In this case, we're going to look at what happens when you go ahead and collapse the cell view hierarchy. Now, for our application, we've gone ahead and we've done a bunch of laying out of different views, and all these views are doing their own drawing and then compositing their information together. But that has some associated expense.
Now, if you want to get the smoothest possible scrolling, you need to collapse your cell view hierarchy. And basically, all that means is you're implementing a single view that knows how to do all of the things that you're doing. all of the drawing. So let's take a look at what that drawing code looks like.
In this case we just look at the Draw Rect for our Movie Summary view. And we can see that it's really pretty straight forward. You know, first we set up the title font that we want to use, we set up the color we want to use for this font, we go and grab the rating image that we want to use, and then it turns into a bunch of, basically, just math to figure out where we want to draw all this stuff. So what's the title point? Where do we actually want to be drawing the title? How do we want the text to either break or not? We take care of drawing that text. We take care of drawing the image into our particular view.
We go ahead and we check to see, hey, does this movie have a cast that we want to list out and show? Does this movie have a synopsis that we want to list out and show? And how about a thumbnail image to show for the actual movie itself as well? So as we can see, it's quite a bit of code to get done all this different drawing. So it's not without some effort that you'll have to get this done.
But let's take a view at our application, and see just how fast it really scrolls. So we'll build and run with this application, and go to our iPhone. And now when we go scrolling through this, we see this is just silky smooth. This is ultra-fast scrolling performance. Really incredible.
Now, you get this fast scrolling performance, but the one downside is that all of these individual rows here are now just flat bitmap graphics. And so you won't get things like cell row highlights by default. You're going to have to handle that logic for yourself. You also won't get things like copy and paste support.
So if you want to implement in that in your application, you're going to have to handle that custom. The other thing you don't get is accessibility, which is a big downside, especially if you've got users with special needs. So you need to make sure that you weigh the considerations here before you necessarily dive down to this method.
But in any event, this is how you're going to get the best scrolling performance possible. Now, if you were to do something like this on a 3GS, you might not notice the difference between the different optimizations that I showed you. But remember, we still sell the iPhone 3G. In fact, it's a very popular device. And so you want to make sure that you're eking out all of these different performance improvements that you can. All right, let's head back to the slides.
Alright, so you've had a chance to see just how easy it is to help your application scroll faster and perform at its best. And you also saw how powerful Instruments is and how you can get lots of detailed information about it to understand how your application is performing. Alright, so in summary, we've taken a look at marking your views as opaque so you can avoid all that unnecessary compositing.
We've talked about drawing minimally, so using set needs display and rect, and actually checking the rectangle that gets passed in. We've talked about reusing table cells, and also using ping files and making optimal use of the hardware. Alright, so we've talked about drawing and scrolling, let's go into application launch.
Now when it comes to application launch, you want to make sure that you're designing your application for fast launch and short use. When you think about iPhone in general, this is a device where people are pulling their phone out at a moment's notice and using your application in context.
Maybe they're on a street corner and they're trying to pull out some valuable piece of information from your actual application. Or they just want to get one really important thing and then get on with their life. So you need to make sure that you consider the user's immediate need. And to do that, you need to make sure that your app launches and quits really fast. Now the key here is to get your UI on screen as quickly as possible.
And actually, for one of the first times in your life, this is a time when it's OK for you to be lazy. In fact, you want to design your apps for that fast launch and short use by being lazy, by doing as little as possible on app launch. So this means if you've got data, load that lazily. Wait until you actually need it.
You want to make sure that you've got a small nib file and that your initial UI is very compact. If you've got other images or assets or different views that you need to load in but they're not on screen at the start, be lazy. Put it off. Wait until you actually need them.
You also want to make sure that you don't perform synchronous network operations in application did finish launching. That is just a recipe for bad times. Doing that might put you in a situation, if the user has a bad cell connection, where it'll take a long, long time to get back data or whatever it is that you happen to be trying to get off the network.
That might take potentially more than five seconds, in which case iPhone is going to wonder why you haven't been servicing the user events and your application might get quit. So please, use the asynchronous version for networking if you must do stuff in the application did finish launching, or kick off an operation in the background to load in your data or do whatever else you need. So let's take a look at a couple examples in Apple's own applications for how we do this. The first is the phone application. Now in the phone application, we only load the visible UI that we actually need at that particular moment.
So if a user launches the app and they're on the keypad by default, we only actually end up loading all the different image assets for the keypad. We're not, for example, loading up all the assets for voicemail or for recents or favorites, and we're not going through and loading up the entire contacts database. So this is an example of how you want to structure your application. You want to make sure that you're only bringing back what's necessary at that moment.
If you take a look at something like YouTube, for example, this is another great example of how to be lazy. So YouTube fundamentally is an application that is dependent on the network. You've got to be able to bring back content, in this case the movies, to show to the user.
But it'd be a real drag if when the user launched the application they just had to sit there and wait and wait and wait for all this content to come in. So instead, what YouTube does is when you launch the application, we actually give you a fully functional UI to begin navigating around.
And we simply put up this indeterminate progress indicator and let the user know, "Hey, we're loading the data." This is great because maybe you don't want to see what was most viewed this week. Maybe you want to see what's featured, for example. So again, be lazy. Defer those network operations. Defer expensive loading. Save it for when the user wants to see it. actually needs that particular data.
Alright, so that's application launch. Let's go ahead and talk about memory usage. Memory is a big topic on iPhone, so let's hop right in. We're going to cover a lot in this section. We're going to take a quick look at an overview of how memory is used on iPhone. We're going to talk about static memory, dynamic memory, your application footprint, and also take a pretty in-depth look at memory warnings.
So let's take a look at how memory is used on iPhone in general. So just from the start, graphics. We've got 12 megabytes of memory that get wired off for graphics usage. On top of that, we've got 32 megabytes of memory that gets used by the kernel. It gets wired by the kernel. It needs a bunch of static known addresses to work with. So, another 32 megabytes right off the stack.
We've got another 12 megabytes or so that are all the different daemons that are running on the phone. So this is everything from Media Server D, which is basically the iPod process, to all the different things that basically make the phone a phone and help all the different subsystems to keep running.
On top of that, you've got Springboard. Springboard is the presentation layer on the phone, and so it's always running, whether it's showing your applications or whether it's actually taking care of showing the different images in your application. So Springboard actually handles all of the display on iPhone, even when your app is the front most.
And now, in addition to all of that, you've got the actual phone process. Remember that whole reason that the user had bought the phone in the first place? Well, that's got to run in the background because we've got to be able to make sure that we listen for incoming calls, voicemails, texts, etc. And that's another four megabytes on top of all of that.
So really, if you look at this usage and you've got a first generation iPhone or an iPhone 3G, you're talking about over half of the available memory is already used. Michael Jurewitz And that's a lot off the top, but remember, this is exactly why the user bought the device, because they want all these great features. So your application has got to deal with basically what's left over, and you need to make sure you're being as efficient as possible.
So let's take a look at the virtual memory system. Now it's there but there are some caveats. There's no swap file on iPhone. So any information that's writable, basically any of the memory that you've malloc in your application, has to stay in memory. That means you're fundamentally limited by the total amount of memory on the device.
Now we do allow you to evict read only pages. So if you have images or other assets that for example you've memory mapped into your process, we can go ahead and evict those if needed. But in general anything that's writable, you're going to have to be very efficient with.
So now, there is a virtual memory system on iPhone. It's not like you're actually sharing memory with other processes on the phone. But you have to do all the management of that memory. So if you're using Objective-C, it's your usual alloc and init, your retain, release, auto-release. There is no garbage collection for you. Now, if you're using standard C or C++, it's everything that you would expect. It's your malloc and free, your new and delete, etc.
Now when it comes to static memory, it's all about reducing things like code size. So what is static memory? Well, usually it's things like your actual application binary. Remember, we actually have to read that binary into memory to be able to execute all the individual instructions. Now, again, this is the binary, not the app bundle itself, so it's just the executable code. Now to reduce that code size, you either want to build your binary for thumb or thumb 2. Now, thumb is a 16-bit instruction set. It's basically a subset of the ARM instruction set that was present on iPhone and iPhone 3G and the iPod Touches.
Now, it's 16-bit, and so ultimately you're saving space on each individual instruction. However, in thumb, there was no native floating point access. And so the processor would actually have to thunk over to a 32-bit mode in order to actually do any floating point, which took some time. So if you were somebody like a game developer, thumb was not something that you generally could use.
Now, on iPhone 3GS, you've got the ability to use Thumb 2. Thumb 2 is a hybrid 16 and 32-bit instruction set that does give you native floating point access. And this is something that you should use all the time, whether you're a regular application developer or a game developer.
Now you also want to make sure that you're not using compiler options that you don't need. In general, you want to avoid C++ exceptions and avoid runtime type identification. Now if you look at how we generally handle the exception model in Objective-C sort of as a whole, but in our frameworks like Foundation and UIKit, we reserve exceptions for exceptional circumstances only, usually things like programmer error.
So if you've got any code that's built around a working model of throwing exceptions left and right as a general mode of communication, you want to move away from that. It's going to be much less memory efficient than you'd like. When it comes to runtime type identification, this is usually a facility that people use in C++ when they're loading in dynamic libraries and they need to identify the type of a class. Well on iPhone, you're only ever using static libraries to begin with, so they're getting linked in as part of the compiler.
But on C++, you're only ever using static libraries to begin with, so they're getting linked in as part of the compiler. So you should have all the information you know to directly refer to these classes at compile time. So in general, avoid runtime type identification if possible as well.
Now when it comes to dynamic memory, you want to make sure that you avoid auto-releasing objects. And the reason for this is that auto-release can be expensive. And now, it's not expensive in the way that you would expect, like it takes the call a long time to execute. The call itself is actually quite fast.
The expensive part is that when you send auto-release to an object, what you're basically saying is that you want this object to also be sent a release message at some point in the future, usually once you've reached the end of the event loop. Now, although that object is going to get that release message, so it is going to get cleaned up, it might not get cleaned up quickly enough.
Maybe later on in some of the same code, you're doing a lot of allocations and you run out of space. Well, if you've got a lot of auto-released objects, there's nothing you can do now to get rid of them. So, in general, you want to prefer direct allocation instead of convenience methods wherever possible. So, for example, with NSString, use alloc and init with format as opposed to string with format.
And you also want to make sure that you just allocate wisely. So it's quite a right to cache objects, but you want to make sure that you're caching the right objects. You don't just want to go willy-nilly and decide that, oh, hey, all these different objects I'm definitely going to need to reference at some point, so I'll hold on to them. That's going to be a recipe for running into memory constraints. So you want to make sure that you cache frequently used objects, but be prepared to release them if you can, if needed, usually when you get a memory warning.
Michael Jurewitz You also just want to make sure that you free objects when you're done with them. If you're finished using an object and the user has gone away from that screen, feel free to go ahead and get rid of it. And you can monitor all this information in ObjectAlloc in Instruments.
Now when it comes to properties, in general you want to make sure that you declare your properties as non-atomic. Now properties by default are atomic. Now this means they provide a certain degree of thread safety. Basically they're making sure that the setting of this value as well as the getting is an atomic access. Basically if you have two different threads that are trying to write two different values into the same property, you're going to get one of those two values. You're not going to get some munged combination of the two.
But under the retain-release model, this level of protection comes at a cost. And so access can be up to 10 times faster by switching to non-atomic as the declaration on your properties. Now the one downside is that now these properties won't necessarily be thread safe for you to use, but if you need that thread safety, if you want that atomicity, you simply need to remove that non-atomic keyword, recompile, and you'll get that all back.
So when it comes to image and layer memory, it's important to remember that the images and layers that you're working with might be small objects by themselves, but they potentially have a very large backing store depending on how many pixels they're actually trying to display on the screen. And in order to display this on the screen, it has to be uncompressed. So you've potentially got a very, very large amount of memory dedicated towards all of this graphics work.
Michael Jurewitz Now it's also important to note that the layer backing stores, these images, etc., that you end up actually displaying onto the screen end up being shared memory between your app and Springboard. So anytime you go to do this drawing, we've got this shared memory region so that Springboard can actually be the thing that draws to the screen.
Now one thing that really bears repetition is the fact that compressed image formats are compressed. Oftentimes people think that an image that's small on disk means that it's also small in memory, but that's just not the case sometimes. It's all about how big the image actually is in terms of resolution. Because remember, it's four bits per pixel to actually draw that image. Red, green, blue, and alpha. So if you've got a 320x480 image, you're already talking about something that's over 600 kilobytes of data.
If you've got an image that's roughly the size of a desktop background, 1600 by 1200, you're talking about over seven and a half megabytes just to actually display that image. And now if you're talking about an image that's come in from a high-res digital camera, say a 10 megapixel image, well that's 40 megabytes to display all that data. Now all of this is to say that you need to make sure that you're properly sizing your image assets for their use in your application.
So if you've just got a small button that you need to show on screen, for example, or some sort of badge in your application, produce the graphic at the resolution that you need. Don't start off with some huge graphic and then just try to scale down for the drawing. You're going to end up wasting a lot of memory in the process.
Now speaking of images, let's take a look at the different image API that you can use and how they behave. So first we'll take a look at UIImagesImageNamed. Now ImageNamed caches the uncompressed image that it loads in memory. This API is actually used by InitWithNibName to load your different images. So if you've got large images that you need to actually load, you should use different initializers as part of ViewDidLoad. And I'll talk about some of those different initializers in a moment.
Now, image named is really quite useful when you've got lots of small, frequently used images. And it's even more useful on 3.0 and above because we're much more aggressive about clearing the cache of these images when a memory warning is received. Now, it's important, though, to make sure in order for this cache to be cleared, you have to make sure that you free any references to these images that you might have in your application.
Otherwise, we're going to assume, hey, you're using this image, you might want to display it soon. This isn't a cache that we can get rid of. So again, though, if you've moved away from this API back in the 2.x days, it's time to come back. The behavior for image named is much better in 3.0 and above.
Next, there's UI image in it with data. Now here you simply pass in the NSData that you want it to load into an image representation, and the method just retains the actual NSData that you pass in. On a memory warning, you should release this NSData if you can easily recreate it. The other option is for you to use the NSData in it with contents of mapped file. This is going to let the system be able to push that mapped file back out to the disk if possible.
But using that can be a little bit difficult, and sometimes people forget to call initWithContents of mapped file, and instead just call initWithContents of file, which is much less performant. So instead, you really should probably be using something like initWithContents of file. In this case, we only retain the path that you actually pass into this method, and we lazily load the image.
Michael Jurewitz Now, on top of all that, we will actually take a look at the path, we'll take a look at the image on disk, and depending on how big it actually is, we'll choose to either memory map that image into memory or not. So you really get a lot of powerful behavior out of this single API. So really probably where you want to spend most of your time is with this API.
So again, in summary, for image named, we cache the uncompressed image in memory. For init with data, we cache that compressed NSData. And init with contents of file, we don't do any caching, because again, we simply load that when needed, and we will load that into memory in different ways, depending on how big the actual image is. With image named, you want to make sure that you release any references.
And if you've got large images, you really should be using something like init with data or init with contents of file. When you get a memory warning, make sure that you try to release the NSData that you were using if you use the init with data method. And with init with contents of file, simply try to release the actual image asset if you can.
Alright, let's talk about low memory warnings. Low memory warnings really are just a fact of life on iPhone. Now you've got one foreground application, but remember there's many processes that are running at a given time, and each one of these processes consumes memory. So let's take a look at a typical usage in your application and how the system would deal with increasing memory usage.
Alright, so let's look at the typical memory consumption in an application over time. Now as total system memory grows, you eventually reach a threshold where we send out a warning, basically asking all the different processes on the system to free up memory if they can. This will go to your process, it goes to all the various background processes on the phone, and they will take whatever steps they can to try to help alleviate some of the memory pressure.
In general though, at this point, usually it's the foreground app or your application that's using the most memory. Now if total system memory continues to grow, eventually we reach a threshold where the frontmost app will get killed. And of course, the system then continues on its merry way.
Now, let's take a look at an overview of how total memory is used on iPhone in general. Now, if we look at just sort of the broader categories here, we can see that it mostly just breaks down to memory that is either free, reclaimable, or already allocated. Basically, free and reclaimable are what the system care about because as that amount of memory gets smaller and smaller is when we're going to start sending out those memory warnings. And eventually, we reach that threshold where the system basically is operating defensively to quit that frontmost app in order to keep the rest of the device stable so that a user can continue to use their phone as a phone.
And again, since 3.0 and above were much better about quitting background processes when a memory warning is received. So things like Safari and Mail will get cleaned up in the background. Now you might have some situations where you get a low memory crash report and you see that these processes were still alive.
So usually this is the case when you've tried to allocate too much memory too quickly. In that case, the system just has to be defensive and it simply has to say, whoa, you've asked for way too much. I'm just going to quit this process here as opposed to trying to service this request and putting the rest of the device in jeopardy.
So when it comes to low memory warnings, don't ignore the signs. And it's important to remember, any app can create these low memory conditions. It might not necessarily just be you. So for example, mail might be going out and fetching a bunch of emails, but that's precisely the behavior that the user specified, that they wanted. They wanted mail in the background to go and retrieve these messages.
So you need to be a good citizen. You need to basically help the system be as efficient as possible. Michael Jurewitz So expect memory warnings. They really are just a normal mode of operation. And make sure you respond to them. A failure to respond will probably lead to your app being terminated. Don't just ignore them.
So what should you do? Well, you should release any object that you can reconstruct. You should release cached objects. You should unload cached resource files, sounds, data, images, etc. And above anything else, don't ask the user to do anything. There's nothing they can do. It's not like they're going to be able to use your application more conservatively. Your memory usage is your memory usage, and you as a developer simply need to be as efficient as possible and to respond to these warnings.
Now luckily, there's lots of different opportunities for you to respond to these things. You've got your app delegate, view controller subclasses, and direct notifications. So let's take a look at the app delegate at application did receive memory warning. So let's say on your app delegate you have some object that you vend back.
In this case, we've got some breadcrumb store, some shared singleton that we're handing back to people who need to get access to these breadcrumbs. So in this case, we've defined our accessor entries to basically check to see if our instance variable is already set. If it's not, we go ahead and we take care of loading that shared store, setting the instance variable, and then returning. And now subsequent requests for this particular accessor will just return the instance variable.
Now if we receive a low memory warning though, we'll go ahead and take care of releasing that particular instance variable and making sure that we set it to nil. Setting it to nil is particularly key. You want to make sure that you nil out that instance variable so that if anybody else tries to talk to that piece of memory, they don't end up talking to a dead object.
You want to make sure that they are just messaging nil, which in this case is safe. So now you've cleaned up the memory, next time somebody asks for the object, you'll reconstruct it just fine. So you've gone from having the object to nicely cleaning it up when a memory warning comes in.
[Transcript missing]
So, we might have an accessor that looks something like this when we want to actually design correctly to release these resources on a memory warning. In this case, we've got this shared instance that we're returning, and again, all we're doing is checking to see if we have the instance variable set. If we don't, we alloc and init the object. We then go ahead and add ourselves as an observer for the notification for UI application did receive memory warning notification. Michael Jurewitz And then finally, we return our instance variable to whoever called us.
Now, you'll notice the selector we passed in was this received memory warning. And so, if this notification happens to get posted, then in our class, we simply need to implement that received memory warning. That method will get called and we'll know, hey, it's time for us to clean up memory. So, we will go ahead and release that instance variable, set it to nil, and then make sure that we remove ourselves as an observer. Michael Jurewitz So, we've got this shared instance variable that we've got to remove ourselves as an observer from the notification center.
So, again, you must respond to low memory warnings. They really are just a fact of life on iPhone, and you need to do your part to help the system be as stable as possible. And also, failure to respond to them might lead to your app being terminated. So, another good reason to make sure that you actually try to relinquish memory. And again, remember that other apps may actually cause these low memory conditions, but this is what the user wants. They've asked for these other behaviors to be happening on the device.
Next, let's talk about auto-release pools. Now, every time an event comes into your application, we take care of creating an auto-release pool for you. And this brackets any of the different allocations that you do in that code so that things get properly released once you finish processing that current event.
However, as you continue to write code, and you might have an algorithm that looks something like this, where maybe you're loading in a bunch of text and iterating through stuff and creating a bunch of temporary objects, you might end up in a situation where you slowly start strangling yourself on all these temporary objects that are sticking around and not going to go away until you've finished processing that entire current event. Now, in order to avoid this kind of situation, you want to take a look at the following.
Take code like this and nest them in their own auto-release pool. It turns out this is really easy to do. It's just NS auto-release pool, alloc init, and then at some point later in your code, you want to bracket that with the pool drain. Drain has the side effect of releasing the pool as well as causing release to be sent to all the different objects that happen to have been auto-released between those two methods.
And now you'll go from creating a bunch of objects... ...and then we hit the for loop and we create some more, to we come right back around the while loop, and they get cleaned up, which is exactly what you want. And of course, then, next time you go through, you're creating some more objects, but again, they get cleaned up. Exactly what you want.
So in summary, for memory, you want to make sure you use the right compiler settings. You want to minimize your object lifetimes as best as you can. Absolutely eliminate memory leaks. Those will just kill you in the end. And make sure that you respond to low memory notifications. All right, so we're moving along. Let's take a look at files and data.
So now when it comes to actually reading data, you want to make sure that you use memory mapping for large files. This avoids loading the entire file into memory at once. You can do this in a number of different ways. You can use MMAP and of course MUNMAP once you're done with a file, or you can use NSDATA's In It With Contents of Mapped File. Michael Jurewitz Now this is very, very important. In It With Contents of Mapped File is cheap. That's what does the memory mapping. In It With Contents of File is expensive.
That's what's going to load the entire thing into memory as is at that moment. So make sure you use the right one. Now, if you've got large data sets or really just about data sets of any size, you really should be using core data. You avoid having to load the entire data set into memory. You get the ability to only load windows of data through a mechanism called batching. And it's really easy to create model objects and add your own custom behavior.
When it comes to download performance, you want to make sure that you don't include unnecessary files. And this might seem kind of silly, but we've seen this more than a few times. In this case, we've actually seen people who have included their subversion directories in their actual applications that they submitted to the App Store. So avoid this. This will most certainly push you over the limit for being able to download over Wi-Fi. Make sure that you're doing a SVN export as opposed to an SVN checkout. before you actually submit something to the App Store.
When it comes to backup performance, you need to make sure that you're storing your data in the appropriate places. So if you have user data, things that you want to be backed up, things that your application is going to need to continue to get at because it contains vital information about what is actually in your application for that user, put it in the documents directory. We'll go ahead and take care of backing that up any time the user syncs their phone with iTunes.
However, if you have temporary files or caches, well, make sure the caches go into library caches and make sure the temporary files go into the temp directory. We'll take care of cleaning those up on regular bases and we won't sync those over as part of the iTunes sync. This is key because this way you're shaving down the amount of time a user is going to have to spend syncing their phone.
I said this earlier, but you also want to make sure that you take advantage of using ping files. Pings are optimized for you. But in addition to that, there are some other files that we also optimize, specifically, P lists. We take property lists and convert them to a compact binary format during compilation with Xcode.
Now, you can use these property lists for storing structured data, things that you would normally put into XML. And when they get converted to this compact binary format, it's actually much more efficient for the frameworks to be able to load this data in the first place. They can merely load this off disk and they don't have to do any complicated text parsing. They just get the objects in memory ready for you to work with.
Now this goes beyond just data that you're storing in your application. If you've got a server that you're talking to, it might make sense for you to have your server send down binary P lists as opposed to sending down big XML, which is expensive to parse. If you can send down a binary P list, it's potentially less bandwidth and it's also less processing work that you have to do on iPhone.
All right, so that's files and data. Let's go ahead and hop into power and battery life. So we're going to cover a few topics here. We're going to talk about the different radios on iPhone. We're going to talk about core location and also take a look at the CPU and the GPU for some general best practice.
Now when it comes to the radios, I'm not talking about listening to music, I'm talking about the actual cellular radios. Now in this case, let's start off with 3G. Sending data on 3G is very expensive. In fact, it's one of the most expensive things that you can do on iPhone. Now the way that 3G is actually designed and specced, the 3G networks actually require the phone to stay in a high power state for a few seconds after that last packet is sent or received.
So you might be transmitting some data and assuming that you're only spending a small amount of time actually doing that transmission, but actually the radio has had to stay up for a lot longer in order to actually complete that transmission. And again, this is just part of the way that 3G works. Now this can be particularly bad if you're just sending small little packets of data regularly, because you might end up keeping the 3G radio in a high power state for a long period of time.
Michael Jurewitz So you want to try to make sure that you optimize your transmissions around 3G. You want to make sure that you're coalescing the data that you want to send into larger chunks, rather than just a thin stream of a few packets at a time. You also want to make sure that you minimize the amount of data that you actually transmit, so using compact formats, for example.
What I like to recommend is that you adopt an algorithm to help you coalesce these transmissions. Now to do that, you probably want to develop some sort of system where basically you input some data that needs to be sent out to the network, and maybe you keep track of the time that you enqueued it, for example.
And then you either wait until you reach a sufficient amount of data to send, or until a sufficiently large period of time has passed that, you know, it's just time to go ahead and take care of sending whatever is here, something that might be a few seconds, five to ten seconds, for example.
Now when it comes to using Wi-Fi, Wi-Fi does use less power than 3G, but it still uses a fair bit. Now the good part is that the Wi-Fi radios can go quiet immediately after transmission. You can detect whether you're on Wi-Fi versus cell, and in this case, this is when you're going to want to perhaps adapt how much data coalescing you actually do. You can get at this through the system configuration framework, and then use that to sort of adapt your different behavior.
Now, 2G has power consumption that is somewhere between 3G and Wi-Fi, but 2G obviously takes longer to send data. Now, the benefit with 2G is that the 2G radio can actually go quiet immediately once it's done transmitting data, but again, the actual transmission took longer, so it's a bit of a wash in the end. Ultimately, you'll still want to make sure that you're coalescing activity when you're on 2G. Alright, let's talk about core location.
So lots of apps use Core Location, and with good reason. It's a fantastic technology. You get the ability to help change how your application works for your user based on where they are in the world and provide really rich, context-sensitive information to them. Now Core Location lets you know where a user is to varying degrees of accuracy. And it's these different degrees of accuracy that have different power considerations. So you want to make sure you use the least amount of accuracy necessary for what you're trying to accomplish.
Now the default for Core Location is to use this CL location accuracy best, i.e. the most expensive. So chances are you want to go ahead and move down to something a little less precise depending on your application. If, for example, you just care that somebody is in the city of San Francisco, you don't want to ask what street corner in San Francisco they happen to be standing on. So use something like CL location accuracy kilometer or perhaps 100 meters.
You also want to make sure that you properly set the distance filter. Now, the distance filter is going to dictate how often CoreLocation tries to go back and reassess a user's location based on how far we think they've actually moved. So you want to set this appropriately. For example, if all you care about are particularly large chunks of movement, then don't set the distance filter to something like 1, which would be one meter, for example.
Now, the default here is the distance filter set to none, which receives all movement updates. So make sure you set this to something that's reasonable. Having all the movement updates come in takes a lot of CPU time and also just takes a lot of power from the device.
Now most importantly, if all you need is a single fix on where a user happens to be, get that location fix and then call Stop Updating Location. CoreLocation manages all the interaction with the GPS power for you, and so by calling Stop Updating Location, CoreLocation now knows that you're done and can stop using the GPS.
Alright, let's talk about the CPU and the GPU. So it's important to remember that every single increase in performance in your application, everything that you can make faster, results in less time on the CPU. And less time on the CPU means more time the CPU can go to sleep, which means less power consumption overall. So you need to go in and try to eke out every single performance improvement you can in your app. Every percentage that you shave off translates directly into better battery life for your user.
Now when it comes to polling versus events, really polling is something you should avoid if at all possible. iPhone OS is highly event based. We've got events for almost everything that you'd want to listen to. So try to avoid polling if at all possible. You want to subscribe to events that actually have a notification and we'll make sure we deliver the appropriate information to you.
But if you must poll, make sure you use a timer with a low frequency. So something on the order of a few seconds or more, not so much. something that's 30 times a second, 60 times a second, etc. A great example of this in iPhone OS 3.0 is the new Shake API. So instead of having to monitor the accelerometer directly, you now can get specific notifications for certain types of motion from the device.
You also want to try to make sure that you are being as bursty as possible with your actual execution. Now, this is similar to how you would work with the network traffic that I talked about earlier. You want to try to consolidate that CPU usage into these different bursts.
This allows the CPU to enter an idle state, and it may require some restructuring of your code or a different algorithm, but it can have very positive impact. For example, when we actually play back audio through something like this, we can see that the CPU is being bursted. For example, when we actually play back audio through something like this, we can see that the CPU is being bursted.
For example, when we actually play back audio through something like this, we can see that the CPU is being bursted. Now when it comes to the GPU, you want to make sure that when you're using OpenGL that you pick a fixed frame rate, usually something like 30 fips.
And if a frame hasn't changed, don't redraw it. So if you've got like a chess game, for example, where the user's going to be spending most of their time looking at the pieces and trying to figure out where they want to move, if you don't have to redraw, don't.
Alright, so we looked at a bunch of different information about the radios and how to coalesce your data, core location and only using the accuracy that you need and making sure that you unsubscribe, and for the CPU and the GPU, optimizing as much performance as you can and being bursty. So we've looked at a lot of topics today. We talked about drawing and scrolling to help your application perform even faster in those table views. We took a look at application launch and how you can be lazy.
We took a really in-depth look at memory usage and talked a lot about memory warnings and how you can help relinquish memory and help the system out. We took a look at files and data and being as fast as possible with how you work with your data. And finally, we looked at power and battery life and the different subsystems and how you work with them. Again, you can always reach me if you have any questions at [email protected] or feel free to consult the iPhone dev portal at developer.apple.com/iphone.