[alert color=”yellow”]Issue #1 of my Newsletter comes out at the end of this month! It’ll be a monthly publication, include the top articles worth reading, and will have a few extra tips to help you. Be sure to subscribe to get it (and other cool perks, like free guides!)![/alert]

Every iOS developer inevitably runs into two common issues during development:

  • Things begin slowing down, bringing with it noticeable stuttering and delays.
  • Crashes emerge from ballooning memory usage.

We want our apps to be amazing, right? While there are other issues that can haunt development, those two can turn the tide against us quickly. Apple provides a great monitoring tool called instruments. Instruments give us the ability to watch a wide array of areas within our app. This helps to pinpoint what exactly could be causing a particular problem.

If you’re new to the iOS landscape I encourage you to experiment with instruments. In this post we’re going to cover the 3 instruments I routinely use with every app I lay my hands on. By the time you’ve finished this article you’ll know how to tackle the performance side of iOS development.

  • You’ll learn how to use the Time Profiler to measure the time it takes to execute code.
  • You’ll see how quickly memory can add up, potentially crashing your app, by using the Allocation instrument.
  • You’ll discover that ARC (automatic reference counting) isn’t so automatic when we dive into the Leaks instrument.

The Example App

I built this example app with the purpose of highlighting the areas these instruments target. My hope is that you can get a sense of how the instruments behave. One of the things that confused me early was just figuring out what I was looking at.

Grab the same project & follow along!

[alert color=”yellow”]Grab the same project from github.com & follow along![/alert]

Introducing the Time Profiler

I touched on measuring speed in my article 10 Actionable Performance Tips To Speed Up Your Table View. The Time Profile instrument captures stack trace information depending on interval (default is 1ms). It provide a decent approximation for how much time a given method took by comparing the state of the stack trace against the interval.

Sorting

In the following example we’re exploring two sorting algorithms; insertion sort, and bubble sort.

The example will give you a general awareness of the impact adding and removing items with an array can have. The methods themselves may not take a lot of time, but behind the scenes there is a lot going on.

Profile Sorting

So what in the world is going on here? In this case we have a situation where what we are doing is causing the system to perform a lot of tasks behind the scenes. We are seeing a lot of indirect dependencies that our code needs. Everything in the list above is a system library that our code relies upon. Dealing with large data sets can be expensive, so offload heavy operations into the background. If you haven’t noticed yet, this code is on a background thread. In the above call stack you’ll see dispatch_worker_thread3. If you were to execute this code on the main thread your app would come to a standstill.

In fact. Try it.

Grab the sample app and remove the dispatch_async calls. This will keep everything on the same thread. You’ll notice is that the status label doesn’t update until a few seconds after you launch the screen.

Image Loading

Our second example now focuses on image loading.

Images Stack Trace

Here we have 3 image loading methods.

  • loadSlowImage1 : Downloads an image from a given URL path
  • loadImage2 : Loads an image from the local bundle (important: this is not cached by the system)
  • loadFastImage3 : Loads an image that is cached by the system

To verify we get the speed we expected we can run the time profiler to look at a few things. In this example I load up the second example in the list (image loading) and reload it 10 times. This’ll give us a good amount of data to look at. I then zone in on the area that I care about (as you can see from the selection area). If we double click on the reload method (the line I have highlighted above), we’ll move to our code to check time spent there.

Image Profile Time

With the image above you can see where the app spent the most time. There isn’t much you can do with optimization here, but you can recognize the value of caching. If you’re needing to call loadSlowImage: many times it pays to cache that image away so you’re not doing so much.

Additional Notes

A few things to point out with this instrument.

Instruments Calltree Settings

These options are not selected by default. When you launch this instrument you’ll want to make sure to select them. This will help you find your methods (which will likely the main issue).

  • Separate by Thread: You really want to isolate operations per thread so you know which thread is the issue. This is especially important for the main thread (where all of the interface drawing happens). Blocking the main thread is the #1 reason for stuttering and freezing.
  • Invert Call Tree: You want to see the most expensive operations. This flips the call tree to show you the methods that were originally started with.
  • Hide Missing Symbols: This will help declutter your view. Basically, if the dSYM file cannot be found (or system framework), you’ll see strange hex values that are useless. This option will hide those.
  • Hide System Libraries: This option will let you focus on the symbols that are directly relevant to your code.
  • Flatten Recursion: Instead of seeing a recursive function as multiple entries (since a recursive function calls itself), you’ll see a single entry.
  • Top Functions: This will let you focus on your most time consuming methods.

Target Area

Additionally. The first image produce results for the entire time we tested. Often times you need to focus in on an area. To do that hold Option and Click > Drag on the timeline (showed above). This is a much easier way to focus in on what matters.

Forget an instrument?

Add Instrument

You can click the plus icon anytime to add an instrument.

Hello Allocations

Often times, especially with photo apps, you’ll run into scenarios where you must download a lot of images from a server. If you’re not careful memory usage can balloon dramatically. You must make sure that you are caching images to the side that you expect to reuse. Let’s look at the memory usage of our image example above.

Allocation Profile

Here you can see spikes of memory usage each time we press reload. We allocate a bunch of memory, replacing existing images, which then deallocate the memory we were using. As you can imagine this is highly inefficient. Imagine if we were downloading much bigger files.

If you look at the line ImageIO_PNG_Data you can see that we have 9 active image objects consuming 12.38 MB of memory. This is ballooning our heap memory allocation, which is memory that is not being released/cached by the system. If you look at how a caching library works you’ll see this in action.

Allocation Cache

In this example I’m using a caching library. I still pressed reload 5 times, but you’ll notice that we’re not holding onto any image data objects (ImageIO_PNG_Data). Our heap size is only 2.51 MB compared to 14.61 MB in the previous example. Anonymous virtual memory is the system trying to be smart by keeping a chunk of memory available in anticipation that we’ll immediately use it again. To prevent crashes you want to avoid your heap from ballooning at a rapid pace.

You’ll also notice by loading our images asynchronously we’re not waiting for all the images to download to reveal them (like first allocation example). Most image caching libraries kicks this work to the background so you’re not spending precious main thread cycles. I’m using a Swift library Haneke for this example.

Leaking Memory

Even though Apple introduced Automatic Reference Counting (ARC), you need to be aware that the potential for leaks do remain. It’s not a bullet proof system! Even if you’re using Swift.

Explaining how memory works is beyond the scope of this article so please make the jump to Apple’s documentation to review more about ARC and memory. Visit the article and type the examples so that you have deep understanding of how this works.

I do want to share one basic example so that you can see a leak in action. First, on the most fundamental level a leak happens when you create objects that have a strong reference to each other. When one object is released, the other object is not allowed to be released since it’s also classified strong.

In the given example we have 2 objects. They both have a variable that own a strong reference to each other. When we release one of them we fail to remove the reference from the other. This leads to a memory leak since ARC can’t determine if we were officially done with the remaining object.

Leaks

We fix this by assigning weak to one of the variables so that it doesn’t add a retain cycle.

I often see leaks bite developers when it comes to view controller management. They may think they moved to a new controller, releasing the old controller, but it was never deallocated. This can leave a ton of objects behind. It pays to just run through your app with the leaks tool to clean up any inadvertent strong reference cycles.

Other Instruments

By no means are these the only instruments you should spend time with. Apple has provided several worthwhile instruments that can help you with your development. It really depends on what you’re doing and where you need insight.

  • Core Data : Examines fetches, cache misses, and saves. This is a great way to visually see if you’re saving far more frequently than needed.
  • Cocoa Layout : Observes constraint changes to help you determine where layout code broke.
  • Network : Observes TCP/IP and UDP/IP connections
  • Automation : Lets you create and edit test scripts to automate user interface tests in your iOS app
  • And many more. I assume you will primarily use the above instruments, but feel free to spend time getting to know the others I didn’t list.

Wrapping up

To be very clear, I don’t want to advocate pre-mature optimization. Don’t worry about using these tools if you are not experiencing slowness with your app. Unless you have some time to do sanity checks. When you start to notice choppy behavior then dive into a few instruments to determine where things are going awry.

Takeaway

If you’re a beginner it pays to spend time with instruments to get a feel for how it all works. This will set you up for a much smoother experience in the future when things really matter! These are invaluable tools that will help you create an even better user experience. Given the high bar that exists on the app store it pays to focus your attention on making a smooth experience.

Your Turn

What are your top 3 instruments? Any helpful tips?

[alert color=”yellow”]Reminder: Issue #1 of my Newsletter comes out at the end of this month! It’ll be a monthly publication, include the top articles worth reading, and will have a few extra tips to help you. Be sure to subscribe to get it (and other cool perks, like free guides!)![/alert]

  • Useful tips. But the sample project is not working in the latest Xcode.

  • Marcelo Salloum Dos Santos

    Awesome guide!

    It really helped me, thanks a lot!