2011 • 53:54
The best iOS apps are not only beautiful and well designed, they also launch quickly, present a highly responsive interface, and use memory efficiently. Master the techniques to diagnose and fix common performance problems before your customers see them. Learn the performance hit-list you should apply to every one of your apps before it goes out the door.
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.
Hello and welcome to your iOS App Performance Hitlist. I'm Michael Jurewitz, the evangelist responsible for developer tools and performance. The best iOS apps are not only beautiful and well designed, they also launch quickly, present a highly responsive interface, and use memory efficiently. Today, we're going to focus on teaching you the skills and techniques you need to know to make your application perform great.
From working with the Time Profiler and Core Animation instruments to taking an in-depth look at how your application is using memory, you'll gain a better understanding of how to use instruments to track down performance issues and ship a better app. So let's get started. So obviously, we're here today to talk about performance. Now, I'm here to tell you that performance is the number one most important thing that you can pay attention to in your application.
Beyond having a great design, beyond having a great idea, your app needs to perform wonderfully. At the end of the day, if you've got a bunch of pretty pixels but your app performance is really, really bad, no one's going to care that your graphics look so good. They're just going to be paying attention to the poor performance of your app.
What is performance, you might be asking? Well, performance is many things. It's about being fast, doing things before the user even expects them to be finished. It's about being responsive, so that as you interact with the screen, you're providing that kind of feedback immediately. And it's about being efficient. It's about making sure that you're using only the system resources that you need to solve a task and not going all over the place using tons and tons of resources.
And more than anything, performance matters because your customers notice. And they leave you things like app reviews that say things like slow and buggy, crashes all the time, and the dreaded one star. You don't want to be a one star app, so you need to be paying attention to the performance of your application and making sure that you fix performance related issues.
Michael Jurewitz And that's exactly what we're here today to try to teach you how to do. So, in this presentation, we're going to show you how to test and measure performance. I'm going to show you how to help your application launch faster and figure out what may be slowing down your launch time. I'm going to show you how to reduce your memory footprint and understand what memory you're using in the first place. And I'm going to show you how to identify common graphics problems and fix them.
them. So let's talk a bit about measuring performance. So first of all, what should you measure? Well, the answer really is everything. You need to be paying attention to processing time, to memory usage, to graphics performance and correctness, to disk IO, to battery life, all of it. And the best part about all of this is that you've got an application to help you do that in Instruments. Instruments is your one-stop shop for all your performance needs. It gives you a holistic view of your application's performance so you can see things like processing time, memory, graphics, battery life, all this information together in one place, side by side.
Now when it comes to techniques, what kinds of techniques should you be using when you measure performance? Well, first and foremost, Don't guess. No, no, actually I'm really, really serious about this. Don't guess. You've got a fantastic tool that's going to help you pinpoint exactly what's slow in your application, exactly what needs work.
So even though you architected the app, even though you wrote all this code, use the tool and make sure the things that you're trying to fix, the things you're trying to change are actually the problem. You don't want to waste time trying to fix something that actually wasn't the problem all along.
Okay, now beyond not guessing, you want to make sure that you focus on simple, repeatable, real-world actions. This is really important, the real-world actions part. You want to focus on all of the different workflows a user is going to be going through in your application. You want to be timing those, measuring those, looking at how things like memory changes over time. You want to be trying to pinpoint what are the things that need improvement. Then you want to make some changes and retest those same actions. Retesting is really important. You don't want to go and make changes and actually end up accidentally making things worse.
And more than anything, you want to keep repeating this until it just feels right. You've got to make sure that your application just feels like the greatest thing to use, that it's fast, that it's efficient, that it doesn't stutter when you scroll, etc. That it just feels like you've put lots of love and care into making it a fantastic user experience.
Now, with all the work that I do with third-party applications and all the opportunities that I have to see how apps are really performing in the wild, it really comes down to three main areas that contribute to applications either behaving poorly or seeming to have poor performance. It comes down to three things that you want to make sure that you do in your development. You want to avoid blocking work. You want to make sure that you minimize memory usage. And you want to make sure that you draw efficiently.
So let's go ahead and hop right into the first one and talk about avoiding blocking work. Perhaps the most important part of avoiding blocking work is to launch quickly. This is your user's first impression of your application, and it immediately communicates so much about the speed, the quality, the care, and the craftsmanship that you've put into your application and all of its user experiences. So you really need to be hyper-focused on this scenario.
From the moment that user taps your application on the home screen, this is the very first opportunity for you to say hello to them, for you to show them the work that you've done. So you want to be focusing on that first launch performance and having that be the best it can possibly be.
You also want to be paying attention to the multitasking resume as well. This is an incredibly common way that users are going to come back to your application. You want to make sure that you don't have performance problems hiding in resume. Now the important thing to remember is that timing is everything. The OS is constantly watching your application. And if you're too slow to get certain things done, iOS may just quit your application.
So, for example, you've got 20 seconds for your application to launch. Now really what this comes down to is this is your application launching, getting through application did finish launching with options, and then beginning to service the event loop. Now I'm here to tell you if your app is taking anywhere near 20 seconds to get all this done, it's time to open up instruments and it's time to figure out what's taking so long. Great launch times should be 2-3 seconds. before the user even notices that they've been waiting.
Now at the same time, you need to be paying attention to things like resume, suspend, and quit. When your app resumes back to the foreground, you only have 10 seconds to begin servicing that event loop, so you need to make sure you're being fast and efficient. As your application goes into the background, you only have 10 seconds to do things like save any files that need saving, or do any additional cleanup that you need to do. Again, you need to make sure you're being as efficient as possible. And when it comes to launching quickly, you need to make sure that you're measuring correctly.
Most importantly, you need to make sure that you're measuring with realistic data sets. It doesn't do any good if any of the performance profiling you're ever doing is on a debug version of your application with absolutely no data for your application to load, or with your application simply connected to the debugger in Xcode. It's actually really important to note, anytime you're debugging your application, Xcode specifically tells the OS not to quit your app if it takes too long.
The assumption is, you're debugging, things might take a while. So it's really important for you to put your application on your device and take it out in the real world and use it. Now when it comes to these data sets, you need to make sure that you're getting a good estimate of what you think is a moderate amount of data that a user will probably have.
And then up that amount of data by 10, 100, or 1,000 times. So that you can actually see, what are the problems? What are the weak points in your application? What's going to really take the longest time to get done? And to do all of this, you want to make sure that you're using the time profiler instrument. Now when it comes to launching quickly, there are a few really important rules to live by.
And really, you want to be asking yourself these questions every time you look at the work that you're doing, an application did finish launching with options, or as part of application resume. The first question should be, is this work that you're doing essential for loading the most basic main user interface? If not, defer it. Do it later. Use something like Dispatch Async.
The next question: Does it involve parsing, loading data, or network activity? Well, first and foremost, you should never be doing any of that work on the main thread. Second of all, these are great candidates to defer. Take this work, wrap it in a block, dispatch async it onto a background queue. Finally, it's important to remember that progress indicators are your friend.
Now, what I'm not suggesting here is that you go and turn every view in your application into a progress indicator while you wait for it to load. That would be a very bad idea. Instead, you should consider doing something like just putting up a single progress indicator for the user, so they know that you're doing work if you need some additional time to, say, parse a file, load in some web content, etc. The key is that you want your application to already be able to be interacted with, and it's okay for you to do that. It's okay for you to tell the user, "Hey, we're still working on things." As long as that doesn't take too long.
Alright, so now let's hop into a demo and take a look at launching quickly and how to figure out why an app may be taking too long to launch. Okay, so we're going to start by taking a look at our iPad first. Now, we want to be paying attention to startup and the application we're going to be using is this inauspiciously named Bad Startup. So, let's go ahead and tap on this app and take a look. Now our app is launching, and it's still launching, and it's still launching, still launching, still launching.
Who knows, will it ever finish? There we go. Okay, so our app finally finished launching. That took a long time, right? If this was your application and this was your customers that had to wait that long, they'd be pretty disappointed. So, let's go ahead and switch back to the Mac and take a look at just exactly what's taking so long.
Now, over here on the Mac, there's one thing I want to show you first and foremost. In Xcode 4.3, Xcode is now just a single dot app. And Instruments, the application we're going to be using for looking at all this performance information, is now actually a part of Xcode. So, to get to Instruments, we're going to go to the Xcode menu and select Open Developer Tool and then select Instruments.
This is going to launch Instruments and we've got our usual interface that we're used to working with. Now, one thing you can do in the dock is go ahead and double tap on Instruments, go to Options and select Keep in Dock. This is a way that you can just keep a reference to Instruments around the next time you just want to launch it directly. Now anytime you're working with any sort of timing information, you want to be working with the Time Profiler instrument. So let's go ahead and choose that.
Now with this instrument selected, we want to go ahead and select our Bad Startup application from our iPad. I'm going to go ahead and make the window a little bit bigger here and click Record. Now what's happening over on our iPad is the application is launching, it's doing a bunch of work, and it's taken a long time to do that work. If we look back at the Mac, and specifically at instruments, we can actually see a visual representation of this work as it's completing. Alright, so our app just finished launching, so I'm going to go ahead and stop the trace.
Okay, so now we have all of this data here for us to work with. And typically for most folks, the problem is you take a measurement, but then the problem becomes, what do you do now that you have this data? How do you actually look at this information? So, let me show you a bit about how to do that.
First and foremost, anytime you're using Time Profiler, you only want to be paying attention to specific windows of time where you are doing the things that you care about. In this case, we really only care about the window of time where our application was launching. And so, if you hold down the Option key and click and drag in the timeline, you can select just the window of time that you care about, so you can focus on just that data.
Next, we really want to be doing with some work here in the table view, but there's not a lot for us to be working with right now. So, let's try to dig in and see what we can do here. Now, new in instruments as part of Xcode 4.3, you'll actually have the call tree uninverted from the start.
So, if you're still using an older version of instruments, you may see something that looks a little bit different. The most important thing is that you want to make sure that you have separate by thread checked here on the left, and that you also have invert call tree unchecked. This is the key for where you want to start.
Now, looking back here at the table view, let's go ahead and add a little bit of information for ourselves. One of the first things that I like to do is pull out what's called the self percentage. Now, the self percentage is what's going to tell us the amount of time that we spent in that particular method itself in the table view, as opposed to, say, any of the other methods that it called through to.
Now when it comes to paying attention to launch performance, what really matters is the time that you spend in the main thread. So now at this point what you really want to be doing is paying attention to this percentage right here under running time. This is the total percentage of time that was spent in the main thread and everything else that it called through to. So using just the arrow keys and the keyboard, let's just start stepping through here and going down the stack. You can see one of the first places that we started was main. This is the main entry point to your application.
Now it's fairly common as you're going through this kind of information to see a bunch of this framework code and get kind of scared. What is this CFRunLoop run-in mode and CFRunLoop run-specific? Well, the most important part is that you just want to continue to follow this large percentage, so let's just continue to do that. All of this is just the details of what happens to be happening in the framework.
Now, as we keep going here, we're eventually going to reach some of our own code. And we can see as we kept going down the chain, boom, here we are, AppDelegate application did finish launching with options. And we see that it took 33.2% of the total time as compared to 34% of the total time in our application that was spent in the main thread. So clearly, this is a huge percentage of the time that we're spending trying to start up.
Now, as we take a look at this menu, you can see that this is the first point that we actually delve into some additional code in our own application. Now, we can see that our view controllers init function was responsible for about 33% of our total startup time. That's a large percentage, all things considered.
Now, when you're looking at startup time, it can be really useful to just hone in on the actual code that you wrote. And so, one of the things you should know is that these arrows on the very right of the different frames that you're looking at, you can actually click on.
And what this does is takes that particular frame and all of the things that it called through to and rebases them at 100%. So now you're basically just looking at the relative impact of all of the code that you wrote that was being called by that method. So once we do that, we can see that that view controller init function was responsible for 99.4% of the time that we're actually spending an application did finish launching with options.
So, as we look at all the information that we called through to here, we can begin to see all of the system APIs that were involved in this slowdown. Now, remember all of the things that I told you that you shouldn't be doing as part of application did finish launching. We see here, for example, we've got an NSData, data with contents of URL, taking 50% of our time. We've got UIImage, image with data, taking 17% of our time.
And NSJSON serialization, JSON object with data, taking 10% of our time. So, we've got potentially network calls, loading of large information, and parsing of data, all happening as part of application launch. That's the last thing that we want to be seeing. So, now, with this information, we know what we need to go and try to improve in our application. But we can actually go one step further. If we go ahead and we double-click on View Content, we can actually go to the View Controller and knit.
We actually can get a view where we can see exactly what percentage of time was being spent in our actual source code that we had written in application did finish launching. Here, you get a chance to actually see how all of these different methods fit together and where we were actually spending all of this time.
In fact, you can see that our NSData, data with contents of URL, was in fact going out and grabbing a remote resource. And that was responsible for loading. And so, when you take both of those and combine them together, that's 72% of our startup time right there in just that single line of code.
As we look up here, we can find that actually the parsing of our JSON didn't take that long, all things considered. But again, this is still something that we don't want to be doing on the main thread. So, now, armed with this information, we can go back into application did finish launching with options, figure out what pieces that we can actually use.
What pieces that we can actually delay or pull out. All of this loading from the network and parsing is a great candidate for doing asynchronously with Grand Central Dispatch. And we can help our application load a lot faster. So, we just had a great look at why an application was taking so long to launch. So, remember, the system will terminate apps that take too long to do basic things like launch, resume, suspend, etc.
Make sure you're collecting data with Time Profiler. And really, the name of the game is trying to do less work. Figure out how you can take the work that you need to be done and either turn it into smaller tasks or tasks that can be done later. Above all else, don't block the main thread. And just make sure that you optimize, optimize, optimize. All right, now let's talk about minimizing memory usage.
So it's important to remember that iOS is not a desktop operating system. It's very, very powerful, but you still have limited memory to work with. These are, after all, all embedded devices. iOS has virtual memory, but there is no swap file. Any of the memory that you allocate has to stay physically resident in the RAM on the device. To help out with that, we have low memory notifications so that your application can know when the system is getting low on memory and when it needs your help to give up resources.
Now reducing the memory footprint in your application is absolutely important. Because iOS has no swap file, all of that memory that you're using counts against you, as well as all of the basic tasks the operating system is trying to get done. And it's important to remember that under memory pressure, the OS is going to start terminating applications. So let's take a quick look at how that works. So in iOS 5, as the amount of dirty memory on the system increases, you first hit a warning level. This is the point where the OS begins to exit background applications.
If the total memory usage on the device continues to go up, we finally hit an urgent level. This is when the frontmost app is warned, and this is your opportunity to respond to this memory warning by freeing up resources. Now if memory usage continues to grow, then eventually we hit a critical point, and this is where the frontmost app will be exited by the system. Now it's important to realize this behavior is subtly different from the way that things worked on iOS 4. On iOS 4, as memory usage got greater and greater, the frontmost app would be notified at the same time as the background applications.
Now what ended up happening is that it became a pretty decent strategy as a frontmost app to simply ignore the memory warnings, because you could probably count on some poor background app being killed instead. But now on iOS 5, when background apps have all been exited, we warn the frontmost app, which is a way of saying that as the frontmost app, your head is on the chopping block. You're the last one that we can ask to help be better about memory. so you need to make sure you're responding to memory warnings.
Now, memory can also have a big impact on responsiveness. Spikes in memory usage may cause other delays that seem rather unlikely, but actually can have a big impact on your application. So, as your application is moving along, if it has a sudden spike in the amount of memory it's using, maybe you went and parsed some large file from the internet, the OS may choose to actually evict some of your executable code from memory, some pieces of your actual app.
Now, this is fine in the short term to try to get back additional resources, but eventually your app needs to keep running, and that needs to be read back into memory. And that paging back into memory can take time, and it can cause jitters in scrolling and other sluggishness.
Now Jetsam is the process on iOS that watches for memory pressure and terminates apps that have excessive memory usage. Now in a multitasking world like we've been on for iOS 4 and iOS 5, it's even more important for you to make sure that you're paying attention to memory and only using the amount that you need. Most importantly, apps that have a small memory footprint will be preserved longer in the background than larger apps.
If you think about it, when we start quitting background processes, we want the biggest bang for our buck. So we're going to go after processes in the background that are using the most memory. So as your application suspends, try to get rid of as much as you can so you can stay safe and stay low.
Now when it comes to reducing memory footprint, there really are three areas you want to focus on. Memory spikes, memory leaks, and abandoned memory. Memory spikes are periods of time where you have very large allocations that you make in very short periods of time. Again, the example we gave earlier was maybe parsing a file that you downloaded from the internet.
You want to try to avoid these for exactly what we just spoke about because that amount of allocation may cause the system to have to get rid of other necessary things in memory. Or if you spike too much, the system may not be able to honor your request at all, in which case your application may get quit.
For memory leaks, this is memory that you've allocated, but at some point you lost a reference to it. And the worst part about these kinds of things is that because you allocated this memory, it's currently counting against you and against your process. But because you don't have a reference to it anymore, you have no way to clean it up. As I like to tell developers, the right number of leaks is zero leaks. You always want to make sure you track these down and make them go away.
A third kind of thing you want to pay attention to is abandoned memory. This is sort of similar to a leak, but you can think of it as memory that you've allocated, you still have a reference to it, but for whatever reason you don't reuse it again. Maybe this might be a cache of some set of views or other data that you create once and then just keep on recreating as opposed to ever actually cleaning it up.
So now, let's look at a demo of using the memory instruments in action so you can figure out how your application is using memory in the first place. Okay, so let's take a look at the memory usage for another one of our applications. We're going to go ahead and open up Instruments. And we're now, instead of choosing one of our default templates, going to go down to the user directory here. And I'm choosing a template that I created for myself called Jury Allocations.
Now, this template isn't special aside from the fact that it contains a bunch of different instruments that I like to work with whenever I'm looking at the memory usage of an application. So as you can see, we simply have the Allocations instrument, the VM Tracker instrument, the Leaks instrument, and Activity Monitor.
And I'll be going through and talking about each one of these and why they're useful. Now, I've got an application on my iPad called The Elements. So I'm going to go ahead and from Instruments, select that app and start profiling it. I'll hit Record. And now, let's go take a look at it on the iPad.
So as you can see, this application is pretty straightforward. It's a table view with a listing of all of the elements. And if we go ahead and we pick one of these, let's say, "Firmium" here, we slide over to the side, and the application's going to load some additional information on that particular element.
So we've got some information being loaded in here. We'll just wait a moment for it to show up. And we see we've got the Wikipedia entry for Fermium. Now let's go through and do this a couple more times. We can take a look at gold, why not? We'll load in all of this information for gold.
And we see we've got now the Wikipedia page for gold. Now I want to focus back on instruments and specifically in the trace that we see that's going on. I'm going to continue to use the elements and I want you to pay specific attention to what's happening with the allocations instrument.
So I'm going to select helium, sure why not, that's a fun one. We see the allocations shoot up for our application. I'm going to go back and now choose a different element, maybe we'll do something like Curium. We see again we've got our allocations shoot up. I'm going to hit back. And we'll repeat this at least one more time here.
Now as we go back, we'll go ahead and select, say, Carbon, why not? And we can see again that our allocations continue to increase. I'm going to go ahead and stop the trace now, and let's take a moment just to look at what's going on in instruments. Now as you're using your application, if you find yourself able to basically be doing the same thing over and over again in your application, and you see that your memory usage, particularly as indicated by allocations, continues to go up and up and up, that's not exactly a good sign. Typically, when you're repeating the same action, you might expect to see memory go up, but then come back down once you're done, and go up again when you repeat it, but then come back down.
In this case, we can see that the memory for our application just keeps steadily marching up. So let's go ahead and let's go through each of the instruments that we have here and show you how you can use them to figure out what might be causing this memory growth.
[Transcript missing]
Generally speaking, you don't want to combine memory instrumentation with something like Time Profiler. The memory instrumentation is taking a trace of your application, and that can have a big impact on the amount of time it takes for something to happen. Activity Monitor is a bit more lightweight, so it's generally okay to use with the Time Profiler.
Now normally if you saw this kind of memory growth, the first thing you'd want to be taking a look at was leaks. However, in this case, we don't have any information to go on. We can see that leaks didn't report any information about any leaks. That's kind of unfortunate because usually when we see a leaked object here, we can also find out what allocated it and then hopefully figure out why it's not getting cleaned up. So in this case, we're going to have to turn to a couple other instruments to try to figure out what might be going on.
So, let's now take a look at the VM Tracker instrument. Now, the VM Tracker instrument is the most authoritative view of how much memory your application is using at any one point in time. Now, we've got the VM Tracker instrument set up to do automatic snapshotting of our application every three seconds. So, this is going to help us see, every three seconds, how that usage is changing over time.
Now the number that you want to pay attention to whenever you're looking at the memory consumption for your application, first you want to order the table by resident size, and then what you want to be paying attention to is dirty resident memory. This is memory that you've allocated, you've changed it in some way, and so it has to stay resident in the physical RAM on the device. And this number right here really is the authoritative view of how much memory your application is using.
Now we can see as we scrub through time here that this number does in fact change. In fact, we can see that earlier in the app, the dirty resident was only about 21 megabytes of memory. But by the time we actually hit closer to the end of using the application, we're sitting closer to 45 megabytes of usage. Michael Jurewitz Now again, this is good in terms of getting an authoritative view of how much memory we're using, but where is this memory coming from? Well, let's take a look at the allocations instrument.
Now if we look at the allocations instrument, we can see there's really a rich set of information here. One of the first things that will pop out to you is the fact that we've got these colored graphs here showing you lots of information about the types of allocations that you are making over here on the left.
Now, the heuristic being used for coloring these graphs is actually pretty simple and straightforward. Blue, generally speaking, is something that looks fairly normal, whereas red or pinkish is something that you might want to be paying attention to. Now, in terms of how we color these things, these graphs get colored red for a number of reasons.
If we see that for a certain allocation type, say in this case a malloc of 48 bytes, that you've made a ton of these types of allocations, but only a very few number of objects are actually left over, that could be a problem. Maybe you're creating too many temporary objects.
Maybe you could be just creating a few of these and reusing them. So this might be something you might want to take a look at, which is why we color it red. At the same time, if you've had a certain allocation type, you've made a ton of these types of allocations, where you've allocated a bunch of objects, and a bunch of objects of that particular type are still around, that's something you also might want to take a look at, because maybe you're leaking those objects. Maybe you're holding onto them longer than you would like, and they're counting against your overall memory usage.
Now, here in this case, there's nothing that really jumps out to us as being a problem. I mean, we could see, for example, that these malloc of 48 bytes look somewhat interesting, but they're not really a problem. But let's take a closer look just to see if there's anything to this.
Now if I go ahead and I click this arrow here next to the malloc48 bytes, I'm going to focus on just the allocations that were made of that particular category. And if I go ahead and pull out the extended detail view, I can actually see the backtrace for all of these different allocations.
Now as you might imagine, there's a lot of information here. If we scroll all the way to the bottom of the list, you can see we've got at least a good 700 objects here that were still alive at the end of our trace. And this is an important point.
When you're working with the allocations instrument, sometimes you want to go and work with the allocation lifespan setting here on the left. In this case, this is set up to filter to show us only the objects that are created and still living. But in our case, we actually had a lot of objects that came out of the same category. So we're going to go ahead and click this arrow here next to the malloc48 bytes.
And if we go ahead and click the add button, we can see that the objects that came out of the same category came and went. So let's take a look at the objects that were created and destroyed. Now when we do that, we see we've got much more information to work with. In fact, there were about 25,000 objects that were created and destroyed that matched that particular allocation type. So let's go ahead and click the add button.
Now as we go through here we can see that we've got a bunch of different kinds of allocations happening. In most cases these are happening just as parts of system frameworks, and there's not too much that we can glean for what exactly may be going on. We can see, for example, that core animation was very, very busy at some point as we were using our application. Again, probably helping us draw our actual pixels to the screen. So let's go ahead and back out of here for a second and look at this problem from a different viewpoint.
[Transcript missing]
Okay, so now we've got several different measurements we've taken. And you'll notice that between each of these measurements, Instruments is telling us how much heap growth happened between each of these. So let's focus on Heap Shot 2 here, because this seems to have a lot of information about having additional objects that were still around. Now as we take a look at this particular heap shot, we can see all of the different allocation types. Non-objects, CFStrings, CF basic hashes, etc.
Now as we look at these non-object types, we actually can get a chance to see what the backtrace was that led to these allocations. Now, I'm just going to quickly go through some of these, and in this case we just kind of want to be looking for some patterns. We see, for example, here on the right that we've got a backtrace in WebCore.
And another backtrace that goes through web core. Another one that goes through web core. There's a decent amount of core animation drawing, but we can see that we're actually going through some backtraces involving web core a decent amount. Now, this should be interesting to us because we know we're using UI WebView in our application.
So maybe there's something to this. Now, this is an important point about memory analysis. Sometimes you just have to look at the data you have and begin to form a hunch about how you had written the code and what the data seems to be pointing at. In this case, let's go ahead and go back to our project here.
And through a bit of the wonders of demo programming, we can actually see that here in our view controller's load view, we were creating a UI WebView, going off to Wikipedia to load it. Adding that WebView to our view controller and then failing to release that WebView. So in this case, this ends up being a one-line fix. We simply need to make sure since we've added that WebView to a view hierarchy that we also clean it up.
Now you may be wondering why something like leaks didn't find this particular leak. And this is another important point. Leaks is a conservative collector. It tries to make sure that anything that it tells you about, it knows for sure is a leak. In the case of views, there's often limited information that leaks can actually use to infer about the life cycle of that view. Views tend to have lots of other objects that have references to them.
So in this case, although leaks was not able to actually show us that this view was leaking, at least we were able to use the allocations instrument to understand what was actually the type of memory that was still around, and it pointed us in the right direction to begin to look to clean up in our own application.
Alright, so we just saw a great demo for how to figure out where your application is using memory. So remember, you really just want to make sure that your application uses the least amount of memory possible. Avoid leaks, avoid abandoned memory, avoid memory spikes, and avoid heap growth. Now, let's talk about drawing efficiently.
Drawing efficiently is one of the most important things that you can do in your application. Inefficient drawing wastes battery life, it's incredibly noticeable to the user, and it gives users a bad impression of your application. Some common problems that I see with drawing really amount to drawing too much transparent content, drawing scaled content, and drawing pixel misaligned content, which we'll talk a little bit more about exactly what that is in one moment. And now, let's go to a demo with the Core Animation Instrument to show exactly how you can discover what's being drawn inefficiently in your application.
Anytime you want to take a look at graphics performance, you want to be using the Core Animation Instrument. So, we're going to go ahead and select the Core Animation Template here in Instruments and hit Choose. Now the Core Animation Instrument is fairly interesting because it's one of the few instruments where you don't actually have to record any of its data.
Instead, we're just going to select the Core Animation Instrument, and you'll notice that we have a set of debug options that we can work with. Now for the rest of this demo, we're going to focus on two of these options in particular. The one at the very top, which is Color Blended Layers, and one kind of in the middle called Color Misaligned Images.
Now we've got an application over on our iPad, and let's go ahead and take a look at that for a moment. Now in Instruments, all I'm going to do is check the checkbox for Color Blended Layers. Now when I do that, you see that immediately the data on the iPad changes. You see everything has been colored red and green.
Now the simple way to read this anytime you're looking at your application is that basically red is bad and green is good. Now specifically what you're seeing for anything that's red is you're seeing content that has been marked as being transparent. Now transparent content ends up being particularly expensive to render.
Now the reason for this is that whenever you're rendering transparent content, you have to render what's on top, followed by what's beneath that, and what's beneath that, and then figure out how all that content should blend together. That can be a very expensive operation. And when you're doing something like trying to scroll through a table view, needing to continually recompute that kind of information can really put a strain on scrolling performance.
Now in this particular app, we see that we've got some UI labels that look like they've been marked as transparent. They've probably been given a background of UI color clear color. We also see that the background for the entire app is transparent. The entire table view cell has probably been marked as being transparent, which is why the very back of it is red as well.
Now in addition to that, we see these thumbnails that we're working with also appear to be red. Again, this is likely because these images were either told that they were transparent by setting is opaque no on the image view, or the images may actually have an alpha channel.
Whenever you're trying to display an image that has an alpha channel, the OS looks at that, assumes the image has an alpha channel for a reason, and will go ahead and render that as a transparent image, even if there doesn't actually happen to be any transparency in that particular image.
Now, when you're looking at this and thinking of ways to correct this, what might you do? Well, first and foremost, the background for the UI table view cell could just be white color. The background for the labels could just be white color. And when the user goes to select a table view cell, the OS will go ahead and take a look at the label, know that it can do something special with that, which in particular is, on selection, it'll change the background color of that label to white. So you don't need to worry about having your labels be transparent just to support things like the selection color.
Now really the name of the game any time you're working with transparency is that you want to try to avoid less than about a half screen's worth of total red area. Once you get beyond about a half screen, you're going to start dropping frames any time you scroll. Now dropping one or two frames as compared to 60 frames per second is not necessarily a huge deal. But as you add more and more transparency, the problem is going to compound and scrolling is going to become even more jerky. Alright, so that's color blended layers.
Now I want to take a look at a different setting. This is going to be Color Misaligned Images. So again, all I'm going to do in Instruments is check this setting for Color Misaligned Images. Now the first thing you'll notice is that a bunch of things have turned yellow.
Now in this case, we're doing some demos on our iPad here, and we've actually scaled up our user interface. So if I go back to just a 1x scale factor, you'll see some of that yellow goes away. So one of the things Color Misaligned Images actually shows us is scaled content. You'll notice that things like the text, images, etc. became yellow when we zoomed up.
Now this can be very useful as you're designing your application because as you go in and look at content, it may actually matter that something turns out to be scaled. In this case, you can see we have this map of a bunch of different photos. If all of these are scaled, this should raise the question in our mind of, why are they scaled? Are these actually very big images that we've just shrunk down and rendered very small? In which case, we're actually wasting a bunch of memory to try to display them in the first case? Or are these actually images that were very small and we've scaled them up? In which case, they're just not going to look good.
So enabling color misaligned images and going hunting for things that are yellow that you are responsible for drawing is a great way to make sure that you're actually drawing things efficiently. It's also a great way to spot things like non-retinographics. you're doing work on iPhone 4 or iPhone 4S.
Now one of the other things that Color Misaligned Images actually shows you are misaligned images. In this case, as we look at the Options panel in our particular app here, you see that we've got a graphic and some text that shows up as magenta whenever we enable Color Misaligned Images.
Now what this is showing us specifically are views that have been drawn at a non-integral location. So let's talk about this for a second. Any time you place a view in the view hierarchy, you give it a frame, which is basically where you want that view to draw itself.
Now a frame consists of a series of floating point coordinates. But it turns out that when you go to draw these views to the actual screen, there are physical pixels on each device that need to light up to actually draw that content to the screen. So you can draw something at a fractional location. So say if you positioned a view at 50.5 or 46.3, what ends up happening is you end up actually straddling a set of physical pixels. And so the OS has to partially light up one side of those pixels and partially light up another side of those pixels.
So your nice, beautiful one pixel line is now sort of one and a half to two anti-aliased. So any place in your application where you see magenta, you want to go in and you want to look for how you happen to be positioning that view. And you simply want to make sure that you're positioning it at an integer location. So just lop off the decimal, either floor or seal it so that you make sure that you actually get an integer value.
Now it's most common to see this kind of thing when you're doing things like trying to center an image or split an image into two. Or split an image into thirds, for example. You can see how you might end up at a .5 offset or a .3 offset.
So any time you see these misaligned images, you want to make sure that you go ahead and you correct them. What's going to happen is your graphics and your text that otherwise looked blurry are actually going to look a whole lot better and more crisp once they're lined up with the physical pixels.
Okay, so we just saw a great demo of using the Core Animation in the OS. And we're going to go ahead and show you how to use the Core Animation instrument to figure out exactly how you're drawing in your application. So remember, avoid wasteful transparency. Avoid transparency with scrolling in particular. Avoid any unnecessary scaling of your graphics content. And ensure pixel alignment.
Okay, so you learned how to use the Time Profiler instrument to optimize startup performance. You saw how you can use everything from Activity Monitor to Leaks, VM Tracker, and Allocations to track down memory-related issues. And finally, you learned how to use the Core Animation instrument to help you pinpoint graphics problems in your app so you can fix them before they go out to customers. I look forward to seeing the great apps that you have in store.