How To Debug A Memory Leak In An iOS App That Is Caused By A Third Party SDK (Part 1 of 3)

Xcode Instruments and Hopper are your best friends

Colin Contreary
Jan 23, 2020 · 6 min read
Image for post
Image for post
Photo by Harrison Broadbent on Unsplash

Yay, memory leaks!

Okay, you normally wouldn’t be excited about those. But today, my friends, is different. Today is a day of empowerment. We’re going to teach you a few tricks to help you debug memory leaks of your own.

This is part one of a three part series on debugging a memory leak in an iOS app that is caused by a third party SDK. While we are focused on debugging an SDK memory leak in this article, the basic ideas can also be applied to a memory leak stemming from your own app.

Note that in this series we’re using an iOS app and some features in Xcode. If you are an Android developer, the information here can still inform your approach to the debugging process. In fact, in Part 3, we will use a non-platform specific tool called Hopper. So there’s something for everyone!

Here’s our roadmap:

👉 Part 1 — We create a simple app called RecipeTime that loads recipes from a website into a WebView. We will then use some of Xcode’s Instruments features — Allocations and Generations — to profile its memory usage.

👉 Part 2 — We add an SDK to our app that introduces a memory leak. We investigate this using Xcode’s Allocations and Generations to learn how to spot a memory leak and what information can be gleaned from these tools.

👉 Part 3 — We load our SDK’s binary into Hopper, a disassembler and static analysis tool. We investigate what caused the memory leak and sum up what we’ve learned about debugging mobile applications.

By the end of this series, you should have a few more tools in your debugging toolkit and feel more confident in your ability to diagnose problems. No more dilly-dallying, let’s get to it!

Part 1 — A Simple App Called RecipeTime

Image for post
Image for post
Our glorious app

Our app does a few things:

  1. It creates a WebView that opens a recipe page on a website
  2. It uses a NavigationDelegate to control which websites the WebView can access
  3. It deletes the WebView
  4. Two seconds later, it creates a new WebView and opens a different recipe page
  5. It repeats this process until we shut down the app

If you’d like to see our app’s code, here it is. (By no means do you have to read it. It’s just here if you’re curious.)

We realize this is a very boring app, but it’s the simplest example we can create for this lesson. It also somewhat resembles an app that allows users to click around and view different websites (e.g. Medium, other news apps, recipes, etc.).

For our purposes, the important part of the app is that it continually makes network requests to view webpages.

Our app uses a small amount of memory. By clearing the WebView after each download, we are only ever storing a single WebView in memory.

So let’s benchmark our app’s performance by going into the Instruments panel of Xcode. From here, we can use Allocations to see all the pieces of our running application.

What is the Allocations Instrument?

Image for post
Image for post

In the top right corner, we see the total memory footprint of the app over time. We can see the peaks are capped, and over time the memory has noticeable drops. The distinct peaks indicate that we do not have a memory leak since the total amount of memory is not increasing over time. The drops indicate that our WebView deletions are working as predicted. The memory drops whenever we delete a WebView because we are able to remove all the assets associated with it (e.g. JavaScript, CSS, images).

If we look at the highlighted line, we see our app only has one WebView. If we let the app run, we will see the WebView instance disappear and be replaced by a new instance every two seconds. This is illustrated in the image below.

Image for post
Image for post

If we would like to take a closer look at how the memory footprint changes over time, we can use the Generations tool.

What are Generations?

Image for post
Image for post

In the above screenshot, let’s focus on the Growth column. This column shows us the additional memory added since the previous Generation. So Generation A’s Growth value would be all the memory created from the app’s launch through loading a few recipes. Generation B should contain only the unique memory created since the marking of Generation A. Generation C should contain the new memory created since Generation B, and so on.

If you’re a little confused about the difference between Allocations and Generations, don’t worry. They’re similar and they’re almost always used together. Allocations helps you understand the totality of what is in memory, whereas Generations lets you dig into exactly how memory changes as the user performs actions.

If we look closely, we see the Growth of Generation A is 5.37MB, whereas every subsequent Growth is under 5KB. That’s good. It means once our app is running, it isn’t creating increasing memory usage.

A great way to think of Generations is to think of an apartment listed for Airbnb. If we wanted to monitor the change in its state, we could take a picture of the apartment before a renter checked in. If we take another picture of the apartment after they check out and compare the two photos, we’ll be able to tell what wasn’t properly cleaned. Likewise, if we took photos every time a renter checked out, over time we would get a sense of whether the apartment is being properly maintained or is getting increasingly messy.

​​Generations is a great tool to help developers monitor their application’s memory usage over time. You can load specific views or go through common user actions to see how the app performs.

We realize our app isn’t perfectly optimized. Creating and destroying WebViews has worse performance in terms of CPU and battery, but it improves the memory usage of an application. Developers must make these optimization decisions themselves.

What Have We Learned So Far

We’ve introduced the Allocations and Generations tools and showed they can be used for checking an app’s memory usage over time.

We’re feeling pretty good that we’ve written a memory-efficient app.

So let’s throw a wrench into it!

In Part 2, we’ll add an SDK that introduces a memory leak. We’ll dive back into Allocations and Generations to see the difference and gain insight in how to debug the problem.

Who We Are

The Mobile Mindset

A place to learn about all things mobile

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store