Charles Proxy Tutorial for iOS

Charles Proxy sits between your app and the Internet. All networking requests and responses will be passed through Charles Proxy, so you’ll be able to inspect and even change data midstream to test how your app responds.

You’ll get hands-on experience with this (and more!) in this Charles Proxy tutorial.

Getting Started

You first need to download the latest version of Charles Proxy (v4.1.1 at the time of this writing). Double-click the dmg file and drag the Charles icon to your Applications folder to install it.

Charles Proxy isn’t free, but everyone is given a free 30-day trial. Charles will only run for 30 minutes in trial mode, so you may need to restart it throughout this Charles Proxy tutorial.

Note: Charles is a Java-based app and supports macOS, Windows and Linux. This Charles Proxy tutorial is for macOS specifically, and some things may be different on other platforms.

Launch Charles, and it will ask for permission to automatically configure your network settings.


Click Grant Privileges and enter your password if prompted. Charles starts recording network events as soon as it launches. You should already see events popping into the left pane.

Note: If you don’t see events, you may have not granted permissions or may have another proxy already setup. See Charles’ FAQ page for troubleshooting help.

The user interface is fairly simple to understand without too much experience. Many goodies are hidden behind buttons and menus, however, and the toolbar has a few items you should know about:


  • The first “broom” button clears the current session of all recorded activity.
  • The second “record/pause” button will be red when Charles is recording events, and gray when stopped.
  • The middle buttons from the “turtle” to the “check mark” provide access to commonly-used actions, including throttling, breakpoints and request creation. Hover your mouse over each to see a short description.
  • The last two buttons provide access to commonly-used tools and settings.

For now, stop recording by clicking the red record/pause button.

The left pane can be toggled between Structure and Sequence views. When Structure is selected, all activity is grouped by site address. You can see the individual requests by clicking the arrow next to a site.


Select Sequence to see all events in a continuous list sorted by time. You’ll likely spend most of your time in this screen when debugging your own apps.

Charles merges the request and response into a single screen by default. However, I recommend you split them into separate events to provide greater clarity.

Click Charles\Preferences and select the Viewers tab; uncheck Combine request and response; and press OK. You may need to restart Charles for the change to take effect.

Try poking around the user interface and looking at events. You’ll notice one peculiar thing: you can’t see most details for HTTPS events!

SSL/TLS encrypts sensitive request and response information. You might think this makes Charles pointless for all HTTPS events, right? Nope! Charles has a sneaky way of getting around encryption that you’ll learn soon.

More About Proxies

You may be wondering, “How does Charles do its magic?”

Charles is a proxy server, which means it sits between your app and computer’s network connections. When Charles automatically configured your network settings, it changed your network configuration to route all traffic through it. This allows Charles to inspect all network events to and from your computer.

Proxy servers are in a position of great power, but this also implies the potential for abuse. This is why SSL is so important: data encryption prevents proxy servers and other middleware from eavesdropping on sensitive information.


In our case, however, we want Charles to snoop on our SSL messages to let us debug them.

SSL/TLS encrypts messages using certificates generated by trusted third parties called “certificate issuers.”

Charles can also generate its own self-signed certificate, which you can install on your Mac and iOS devices for SSL/TLS encryption. Since this certificate isn’t issued by a trusted certificate issuer, you’ll need to tell your devices to explicitly trust it. Once installed and trusted, Charles will be able to decrypt SSL events!

When hackers use middleware to snoop on network communications, it’s called a “man-in-the-middle” attack. In general, you DON’T want to trust just any random certificate, or you can compromise your network security!

There are some cases where Charles’ sneaky man-in-the-middle strategy won’t work. For example, some apps use “SSL pinning” for extra security. SSL pinning means the app has a copy of the SSL certificate’s public key, and it uses this to verify network connections before communicating. Since Charles’ key wouldn’t match, the app would reject the communication.

In addition to logging events, you can also use Charles to modify data on the fly, record it for later review and even simulate bad network connections. Charles is really powerful!

Charles Proxy & Your iOS Device

It’s simple to set up Charles to proxy traffic from any computer or device on your network, including your iOS device.

First, turn off macOS proxying in Charles by clicking Proxy (drop-down menu)\macOS Proxy to uncheck it. This way, you’ll only see traffic from your iOS device.

Next, click Proxy\Proxy Settings, click the Proxies and make note of the port number, which by default should be 8888.

Then, click Help\Local IP Address and make note of your computer’s IP address.

Now grab your iOS device. Open Settings, tap on Wi-Fi and verify you’re connected to the same network as your computer. Then tap on the button next to your WiFi network. Scroll down to the HTTP Proxy section and tap Manual.

Enter your Mac’s IP address for Server and the Charles HTTP Proxy port number for Port. Tap the back button, or press the Home button, and changes will be saved automatically.

If you previously stopped recording in Charles, tap the record/pause button button now to start recording again.

You should get a pop-up warning from Charles on your Mac asking to allow your iOS device to connect. Click Allow. If you don’t see this immediately, that’s okay. It may take a minute or two for it to show up.

You should now start to see activity from your device in Charles!

Next, still on your iOS device, open Safari and navigate to

A window should pop up asking you to install a Profile/Certificate. You should see a self-signed Charles certificate in the details. Tap Install, then tap Install again after the warning appears, and then tap Install one more time. Finally, tap Done.


Apple really wants to make sure you want to install this! :] Again, don’t install just any random certificate or else you may comprise your network security! At the end of this Charles Proxy tutorial, you’ll also remove this certificate.

Note:iOS 10.3 Update: You may receive an error at this point. See this link for a solution.

Snooping on Someone Else’s App

If you are like most developers, you’re curious about how things work. Charles enables this curiosity by giving you tools to inspect any app’s communication — even if it’s not your app.

Go to the App Store on your device and find and download Weather Underground. This free app is available in most countries. If it’s not available, or you want to try something else, feel free to use a different app.

You’ll notice a flurry of activity in Charles while you’re downloading Weather Underground. The App Store is pretty chatty!

Once the app is installed, launch the app and click the broom icon in Charles to clear recent activity.

Enter the zip code 90210 and select Beverley Hills as your location in the app. If you were to use your current location, the URL that the app fetches could change if your location changes which might make some later steps in this Charles Proxy tutorial harder to follow.

There are tons of sites listed in the Structure tab! This is a list of all activity from your iOS device, not just the Weather Underground app.


Switch to the Sequence tab and enter “wund” in the filter box to show only weather traffic.

You should now see just a few requests to Click one of them.

The Overview section shows some request details but not much. Likewise, you won’t see many details in either the Request or Response yet either. The Overview gives the reason why: “SSL Proxying not enabled for this host: enable in Proxy Settings, SSL locations.” You need to enable this.

Click on Proxy\SSL Proxying Settings. Click Add; enter for the Host; leave the Port empty; and press OK to dismiss the window.

Back in the Wunderground app, pull down to refresh and refetch data. If the app doesn’t refresh, you might need to kill it from the multitasking view and try again.

Huzzah! Unencrypted requests! Look for one of the requests with a URL containing forecast10day. This contains the payload that’s used to populate the weather screen.


Let’s have some fun and change the data before the app gets it. Can you get the app to break or act funny?

Right-click the request within the Sequence list, and click the Breakpoints item in the pop-up list. Now each time a request is made with this URL, Charles will pause and let you edit both the request and response.

Again, pull down to refresh the app.

A new tab titled Breakpoints should pop up with the outgoing request. Simply click Execute without modifying anything. A moment later, the Breakpoints tab should again re-appear with the response.

Click on the Edit Response tab near the top. At the bottom, select JSON text. Scroll down and find temperature and change its value to something insane like 9800.

Note: If you take too long editing the request or response, the app may silently time out and never display anything. If the edited temperature doesn’t appear, try again a little quicker.


9800°F is crazy hot out! Seems like Wunderground can’t show temperatures over 1000°. I guess the app will never show forecasts for the surface of the sun. That’s a definite one-star rating. ;]

Delete the breakpoint you set by going to Proxy\Breakpoint Settings.

Uncheck the entry for to temporarily disable it, or highlight the row and click Remove to delete it. Pull down to refresh, and the temperature should return to normal.

Next, click the Turtle icon to start throttling and simulate a slow network. Click Proxy\Throttle Settings to see available options. The default is 56 kbps, which is pretty darn slow. You can also tweak settings here to simulate data loss, reliability issues and high latency.

Try refreshing the app, zooming the map and/or searching for another location. Painfully slow, right?

It’s a good idea to test your own app under poor network conditions. Imagine your users on a subway or entering an elevator. You don’t want your app to lose data, or worse, crash in these circumstances.

Apple’s Network Link Conditioner provides similar throttling capabilities, yet Charles allows for much finer control over network settings. For example, you can apply throttling to only specific URLs to simulate just your servers responding slowly instead of the entire connection.

Remember to turn off throttling when you’re done with it. There’s nothing worse than spending an hour debugging, only to find you never turned off throttling!

Troubleshooting Your Own Apps

Charles Proxy is especially great for debugging and testing your own apps. For example, you can check server responses to ensure you have JSON keys defined correctly and expected data types are returned for all fields. You can even use throttling to simulate poor networks and verify your app’s timeout and error-handling logic.

You’ll try this out using an iMessage app called “Charles in Charge” that I created for this tutorial.

If you’re a child of the ’80s, you may know the popular Charles in Charge comedy starring Scott Baio. The “Charles in Charge” iMessage app uses Microsoft Bing’s Image Search API to provide images of characters that you can send within your iMessages.

You’ll first need to get a free Bing Image Search API key to configure the demo app. Start by creating a Microsoft Cognitive Services account from here. Check your e-mail, and click the verification link to complete your account setup. This is required to generate API keys.

After signing up, click the link for either Get Started for Free or Subscribe to new free trials on your account page (either one may show depending on how you get to the page); select the checkbox for Bing Search – Free; click I agree next to the terms and conditions; and finally, click Subscribe.

After you complete the signup, you’ll get two access keys. Click copy next to either one to copy it to your keyboard. You’ll need this soon.


Next, download Charles in Charge from here and then open CharlesInCharge.xcodeproj in Xcode.

Open the MessagesExtension group, and you’ll see a few Swift files.

CharlesInChargeService manages the calls to Bing to search for images and then uses ImageDownloadService to download each image for use in the app.

In the findAndDownloadImages(completion:) method within the CharlesInChargeService class, you’ll see the subscriptionKey parameter for BingImageSearchService initializer is an empty string.

Paste your access key from the Microsoft Cognitive Services portal here.

Build and run the app within any iPhone Simulator and try it out.

Note: If it’s not already selected, choose the MessagesExtension scheme to build.

In Charles, click Proxy and select macOS Proxy to turn it back on (if it doesn’t show a checkmark already).

Then, click Proxy\SSL Proxying Settings and add to the list.

You next need to install the Charles Proxy SSL certificate to allow proxying SSL requests in the Simulator. Before you do, quit the Simulator app. Then in Charles, click Help\SSL Proxying\Install Charles Root Certificate in iOS Simulators.

Back in Xcode, build and run the project in the Simulator. When iMessage launches, find Charles in Charge and select it. Then, wait for the images to load… but they never do! What’s going on here!?

In the console, you’ll see:

Bing search result count: 0

It appears the app isn’t getting search results, or there’s a problem mapping the data.

First verify whether or not you got data back from the API.

In Charles, enter “cognitive” in the filter box to make it easier to find the Bing Image Search request. Click on the request in the list, select the Response tab, and choose JSON text at the bottom. Look for the JSON entry value, and you’ll see there are indeed search results returned.

So, something strange must be happening within the BingImageSearchService.

Open this class in Xcode and look for where the search results are mapped from JSON to a SearchResultstruct:

guard let title = hit["name"] as? String,
  let fullSizeUrlString = hit["contenturl"] as? String,
  let fullSizeUrl = URL(string: fullSizeUrlString),
  let thumbnailUrlString = hit["thumbnailurl"] as? String,
  let thumbnailUrl = URL(string: thumbnailUrlString),
  let thumbnailSizes = hit["thumbnail"] as? [String: Any],
  let thumbnailWidth = thumbnailSizes["width"] as? Float,
  let thumbnailHeight = thumbnailSizes["height"] as? Float else {
    return nil

Ah ha! The SearchResult will be nil if any of of the keys aren’t found. Compare each of these keys against the response data in Charles. Look closely: case matters.

If you have eagle eyes, you’ll see that both contentUrl and thumbnailUrl don’t have the capital U in the mapping code. Fix these keys and then build and run the app again.


Success! Charles is now in charge!

Remove Charles’ Certificate

In the past, Charles created a shared certificate across everyone’s devices that used it. Fortunately, Charles now creates unique certificates. This significantly reduces the chance of a man-in-the-middle attack based on this certificate, but it’s still technically possible. Therefore, you should always remember to remove the Charles’ certificates when you’re done with it.

First remove the certificate(s) from macOS. Open the Keychain Access application located in the folder Applications\Utilities. In the search box type in Charles Proxy and delete all the certificates that the search finds. There is most likely only one to delete. Close the application when you’re done.

Next remove the certificates from your iOS device. Open the Settings app and navigate to General\Profiles & Device Management. Under Configuration Profiles you should see one or more entries for Charles Proxy. Tap on one and then tap Delete Profile. Repeat this for each Charles Proxy certificate.

Profiles & Device Management isn’t available in the iOS Simulator. To remove the Charles Proxy certificates, reset the simulator by clicking the Simulator menu and then Reset Content and Settings.

Charles Proxy Tutorial for iOS

XCode Instruments with Swift

This Instruments tutorial will show you how to use the most important features of the tool called Instruments that ships with Xcode. It allows you to check your code for performance issues, memory issues, reference cycles, and other problems.

In this tutorial you’re going to learn:

  • How to determine hot-spots in your code using the Time Profiler instrument in order to make your code more efficient, and
  • How to detect and fix memory management issues such as strong reference cycles in your code using the Allocations instrument and the Visual Memory Debugger.

All set? Get ready to dive into the fascinating world of Instruments! :]

Getting Started

For this Instruments tutorial you won’t go through the process of creating an application from scratch; instead, a sample project has been provided for you. Your task is to go through the application and improve it using Instruments as your guide — very similar to how you would go about optimizing your own apps!

Download the starter project then unzip it and open it up in Xcode.

This sample app uses the Flickr API to search for images. To use the API you’ll need an API key. For demo projects, you can generate a sample key on Flickr’s website. Just perform any search at: and copy the API key out of the URL at the bottom – it follows the “&api_key=” all the way to the next “&”.

For example, if the URL is: efea8e7f6dfc6b70bc03d2afb&format=rest&api_sig=f24f4e98063a9b8ecc8b522b238d5e2f
Then the API key is: 6593783efea8e7f6dfc6b70bc03d2afb.

Paste it into the top of the FlickrAPI.swift file, replacing the existing API key.

Note that this sample API key is changed every day or so, so you’ll occasionally have to regenerate a new key. The app will alert you whenever the key is no longer valid.

Build and run the app, perform a search, click the result, and you’ll see something like the following:


Browse through the application and check out the basic functions. You might be tempted to think that once the UI looks great the app is ready for store submission. However, you’re about to see the value that using Instruments can add to your app.

The remainder of this tutorial will show you how to find and fix the issues that still exist in the app. You’ll see how Instruments can make debugging problems a whole lot easier! :]

Time for Profiling

The first instrument you’ll look at is the Time Profiler. At measured intervals, Instruments will halt the execution of the program and take a stack trace on each running thread. Think of it as clicking the pause button in Xcode’s debugger.

Here’s a sneak preview of the Time Profiler:

This screen displays the Call Tree. The Call Tree shows the amount of time spent executing in various methods within an app. Each row is a different method that the program’s execution path has followed. The time spent in each method can be determined from the number of times the profiler is stopped in each method.

For instance, if 100 samples are done at 1 millisecond intervals, and a particular method is found to be at the top of the stack in 10 samples, then you can deduce that approximately 10% of the total execution time — 10 milliseconds — was spent in that method. It’s a fairly crude approximation, but it works!

Note: In general, you should always profile your app on an actual device, instead of the simulator. The iOS simulator has all of the horsepower of your Mac behind it, whereas a device will have all of the limitations of mobile hardware. Your app may seem to run just fine in the simulator, but you might discover a performance issue once it’s running on a real device.

So without any further ado, time to get instrumenting!

From Xcode’s menu bar, select Product\Profile, or press ⌘I. This will build the app and launch Instruments. You will be greeted with a selection window that looks like this:

These are all different templates that come with Instruments.

Select the Time Profiler instrument and click Choose. This will open up a new Instruments document. Click the red record button in the top left to start recording and launch the app. You may be asked for your password to authorize Instruments to analyze other processes — fear not, it’s safe to provide here! :]

In the Instruments window, you can see the time counting up, and a little arrow moving from left to right above the graph in the center of the screen. This indicates that the app is running.

Now, start using the app. Search for some images, and drill down into one or more of the search results. You have probably noticed that going into a search result is tediously slow, and scrolling through a list of search results is also incredibly annoying – it’s a terribly clunky app!

Well, you’re in luck, for you’re about to embark on fixing it! However, you’re first going to get a quick run down on what you’re looking at in Instruments.

First, make sure the view selector on the right hand side of the toolbar has both options selected, like so:

That will ensure that all panels are open. Now study the screenshot below and the explanation of each section beneath it:

  1. These are the recording controls. The red ‘record’ button will stop & start the app currently being profiled when it is clicked (it toggles between a record and stop icon). The pause button does exactly what you’d expect and pauses the current execution of the app.
  2. This is the run timer. The timer counts how long the app being profiled has been running, and how many times it has been run. Click the stop button and then restart the app, you’ll see the display now show Run 2 of 2.
  3. This is called a track. In the case of the Time Profiler template you selected, there’s just one instrument so there’s just one track. You’ll learn more about the specifics of the graph shown here later in the tutorial.
  4. This is the detail panel. It shows the main information about the particular instrument you’re using. In this case, it’s showing the methods which are “hottest” — that is, the ones that have used up the most CPU time.Click on the bar at the top of this area on the word Profile and select Samples. Here you can view every single sample. Click on a few samples, and you’ll see the captured stack trace appear in the Extended Detail inspector to the right. Switch back to Profile when you’re done.
  5. This is the inspectors panel. There are two inspectors: Extended Detail and Run Info. You’ll be learning more about these options shortly.

Drilling Deep

Perform an image search, and drill into the results. I personally like searching for “dog”, but choose whatever you wish – you might be one of those cat people! :]

Now, scroll up and down the list a few times so that you’ve got a good amount of data in the Time Profiler. You should notice the numbers in the middle of the screen changing and the graph filling in; this tells you that CPU cycles are being used.

No table view is ready to ship until it scrolls like butter!

To help pinpoint the problem, you’ll set some options. Click the Stop button and, below the detail panel, click the Call Tree button. In the popover that appears, select Separate by Thread, Invert Call Tree and Hide System Libraries. It will look like this:

Here’s what each option is doing to the data displayed in the table to the left:

  • Separate by State: This option groups results by your application’s life cycle state and is a useful way to inspect how much work your app is doing and when.
  • Separate by Thread: Each thread should be considered separately. This enables you to understand which threads are responsible for the greatest amount of CPU use.
  • Invert Call Tree: With this option, the stack trace is considered from most recent to least recent.
  • Hide System Libraries: When this option is selected, only symbols from your own app are displayed. It’s often useful to select this option, since usually you only care about where the CPU is spending time in your own code – you can’t do much about how much CPU the system libraries are using!
  • Flatten Recursion: This option treats recursive functions (ones which call themselves) as one entry in each stack trace, rather than multiple.
  • Top Functions: Enabling this makes Instruments consider the total time spent in a function as the sum of the time directly within that function, as well as the time spent in functions called by that function. So if function A calls B, then A’s time is reported as the time spent in A PLUS the time spent in B. This can be really useful, as it lets you pick the largest time figure each time you descend into the call stack, zeroing in on your most time-consuming methods.

Scan the results to identify which rows have the highest percentage in the Weight column. Notice that the row with the Main Thread is using up a significant proportion of CPU cycles. Unfold this row by clicking the small arrow to the left of the text, and drill down until you see of one of your own methods (marked with the “person” symbol). Although some values may be slightly different, the order of the entries should be similar to the table below:

Well, that certainly doesn’t look too good. The vast majority of time is spent in the method that applies the ‘tonal’ filter to the thumbnail photos. That shouldn’t come as too much of a shock to you, as the table loading and scrolling were the clunkiest parts of the UI, and that’s when the table cells are constantly being updated.

To find out more about what’s going on within that method, double-click its row in the table. Doing so will bring up the following view:

Well that’s interesting, isn’t it! applyTonalFilter() is a method added to UIImage in an extension, and, a lot of time is spent invoking the method that creates the CGImage output after applying the image filter.

There’s not really much that can be done to speed this up: creating the image is quite an intensive process, and takes as long as it takes. Let’s try stepping back and seeing where applyTonalFilter() is called from. Click Root in the breadcrumb trail at the top of the code view to get back to the previous screen:

Now click the small arrow to the left of the applyTonalFilter row at the top of the table. This will show the caller of applyTonalFilter. You may need to unfold the next row too; when profiling Swift, there will sometimes be duplicate rows in the Call Tree, prefixed with @objc. You’re interested in the first row that’s prefixed with the “person” symbol, which indicates it belongs to your app’s target:

In this case, this row refers to the results collection view’s (_:cellForItemAt:). Double-click the row to see the associated code from the project.

Now you can see what the problem is. Take a look at line 74; the method to apply the tonal filter takes a long time to execute, and it’s called directly from collectionView(_:cellForItemAt:), which will block the main thread (and therefore the entire UI) each time it asks for a filtered image.

Offloading the Work

To solve this, you’ll take two steps: first, offload the image filtering onto a background thread with; then cache each image after it’s been generated. There is a small, simple image caching class (with the catchy name ImageCache) included in the starter project, that simply stores images in memory and retrieves them with a given key.

You could now switch to Xcode and manually find the source file you’re looking at in Instruments, but there’s a handy Open in Xcode button right in front of your eyes. Locate it in the panel just above the code and click it:

There you go! Xcode opens up at exactly the right place. Boom!

Now, within collectionView(_:cellForItemAt:), replace the call to loadThumbnail(for:completion:) with the following:

ImageCache.shared.loadThumbnail(for: flickrPhoto) { result in

  switch result {
    case .success(let image):
      if cell.flickrPhoto == flickrPhoto {
        if flickrPhoto.isFavourite {
          cell.imageView.image = image
        } else {
          if let cachedImage = ImageCache.shared.image(forKey: "\(") {
            cell.imageView.image = cachedImage
           else {
               if let filteredImage = image.applyTonalFilter() {
                 ImageCache.shared.set(filteredImage, forKey: "\(")
                   DispatchQueue.main.async {
                     cell.imageView.image = filteredImage
  case .failure(let error):
    print("Error: \(error)")

The first section of this code is the same as it was before, and is concerned with loading the Flickr photo’s thumbnail image from the web. If the photo is favorited, the cell displays the thumbnail as-is. However, if the photo isn’t favorited, the tonal filter is applied.

This is where you changed things: first, you check to see if a filtered image for this photo exists in the image cache. If yes, great; you display that image in the image view. If not, you dispatch the call to apply the tonal filter onto a background queue. This will allow the UI to remain responsive whilst the images are filtered. When the filter has been applied, you save the image in the cache, and update the image view on the main queue.

That’s the filtered images taken care of, but there’s still the original Flickr thumbnails to be taken care of. Open up Cache.swift and find loadThumbnail(for:completion:). Replace it with the following:

func loadThumbnail(for photo: FlickrPhoto, completion: @escaping FlickrAPI.FetchImageCompletion) {
  if let image = ImageCache.shared.image(forKey: {
  else {
    FlickrAPI.loadImage(for: photo, withSize: "m") { result in
      if case .success(let image) = result {
        ImageCache.shared.set(image, forKey:

This is quite similar to the way you handled filtered images. If an image already exists in the cache then you call the completion closure straight away with the cached image. Otherwise, you load the image from Flickr and store it in the cache.

Re-run the app in Instruments by navigating to Product\Profile (or ⌘I – remember, those shortcuts will really save you some time).

Notice that this time Xcode doesn’t ask you for which instrument to use. This is because you still have a window open for this app, and Instruments assumes you want to run again with the same options.

Perform a few more searches, and notice that this time the UI is not quite so clunky! The image filter is now applied asynchronously and the images are cached in the background, so once they only have to be filtered once. You’ll see a number of dispatch_worker_threads in the Call Tree – these are handling the heavy lifting of applying image filters.

Looks great! Is it time to ship it? Not yet! :]

Allocations, Allocations, Allocations

So what bug are you going to track down next? :]

There’s something hidden in the project that you probably don’t know is there. You’ve likely heard about memory leaks. But what you may not know is that there are actually two kinds of leaks:

  1. True memory leaks are where an object is no longer referenced by anything but still allocated – that means the memory can never be re-used.
    Even with Swift and ARC helping manage memory, the most common kind of memory leak is a retain cycle or strong reference cycle. This is when two objects hold strong references to one another, so that each object keeps the other one from being deallocated. This means that their memory is never released.
  2. Unbounded memory growth is where memory continues to be allocated and is never given a chance to be deallocated. If this continues forever, then at some point the system’s memory will be filled and you’ll have a big memory problem on your hands. On iOS this means that the app will be killed by the system.

The next instrument covered in this tutorial is the Allocations instrument. This gives you detailed information about all the objects that are being created and the memory that backs them; it also shows you retain counts of each object.

To start afresh with a new instruments profile, quit the Instruments app, don’t worry about saving this particular run. Now press ⌘I, select the Allocations instrument from the list and click Choose.

You should now be presented with the Allocations instrument. It should look familiar to you because it looks a lot like the time profiler.

Click the Record button on the top left to run the app. This time you’ll notice two tracks. For the purposes of this tutorial, you will only be concerned with the one called All Heap and Anonymous VM.

With the Allocations instrument running on the app, make five different searches in the app but do not drill down into the results yet. Make sure the searches have some results. Now let the app settle a bit by waiting a few seconds.

You should have noticed that the graph in the All Heap and Anonymous VM track has been rising. This is telling you that memory is being allocated. It’s this feature that will guide you to finding unbounded memory growth.

What you’re going to perform is a “generation analysis”. To do this, click the button called Mark Generation. You’ll find the button at the bottom of the detail panel:

Click it and you will see a red flag appear in the track, like so:

The purpose of generation analysis is to perform an action multiple times, and see if memory is growing in an unbounded fashion. Drill into a search, wait a few seconds for the images to load, and then go back to the main page. Then mark the generation again. Do this repeatedly for different searches.

After a drilling into a few searches, Instruments will look like this:

At this point, you should be getting suspicious. Notice how the blue graph is going up with each search that you drill into. Well, that certainly isn’t good. But wait, what about memory warnings? You know about those, right? Memory warnings are iOS’s way of telling an app that things are getting tight in the memory department, and you need to clear out some memory.

It’s possible that this growth is not just due to your app; it could be something in the depths of UIKit that’s holding onto memory. Give the system frameworks and your app a chance to clear their memory first before pointing a finger at either one.

Simulate a memory warning by selecting Instrument\Simulate Memory Warning in Instruments’ menu bar, or Hardware\Simulate Memory Warning from the simulator’s menu bar. You’ll notice that memory usage dips a little, or perhaps not at all. Certainly not back to where it should be. So there’s still unbounded memory growth happening somewhere.

Instruments: Talkin’ Bout My Generation

The reason for marking a generation after each iteration of drilling into a search is that you can see what memory has been allocated between each generation. Take a look in the detail panel and you’ll see a bunch of generations.

Within each generation, you’ll see all the objects that were allocated and still resident at the time that generation was marked. Subsequent generations will contain just the objects since the previous generation was marked.

Look at the Growth column and you’ll see that there is definitely growth occurring somewhere. Open up one of the generations and you’ll see this:

Wow, that’s a lot of objects! Where do you start?

Easy. Click the Growth header to sort by size, make sure the heaviest objects are at the top. Right near the top of each generation, you’ll notice a row labeled ImageIO_jpeg_Data, which certainly sounds like something dealt with in your app. Click on the arrow on the left of ImageIO_jpeg_Data to display the memory addresses associated with this item. Select the first memory address to display the associated stack trace in the Extended Detail inspector on the panel to the right:

This stack trace shows you the point when this specific object was created. The parts of the stack trace in gray are in system libraries; the parts in black are in your app’s code. Hmm, something looks familiar: Some of the black entries show your old friend collectionView(_:cellForItemAt:). Double click any of those entries, Instruments will bring up the code in its context.

Take a look through the method, you’ll see it calling set(_:forKey:) on line 81. Remember, this method caches an image in case it is used again later on in the app. Ah! Well that certainly sounds like it could be a problem! :]

Again, click the Open in Xcode button to jump back into Xcode. Open Cache.swift and take a look at the implementation of set(_:forKey:).:

func set(_ image: UIImage, forKey key: String) {
  images[key] = image

This adds an image to a dictionary which is keyed on the photo ID of the Flickr photo. But if you look through the code, you’ll notice that the image is never cleared from that dictionary!

That’s where your unbounded memory growth is coming from: everything is working as it should, but the app never removes things from the cache — it only ever adds them!

To fix the problem, all you need to do is have ImageCache listen to the memory warning notification that UIApplication fires. When ImageCache receives this, it must be a good citizen and clear its cache.

To make ImageCache listen to the notification, open up Cache.swift and add the following initializer and de-initializer to the class:

init() {
   NotificationCenter.default.addObserver(forName: Notification.Name.UIApplicationDidReceiveMemoryWarning, object: nil, queue: .main) { [weak self] notification in
    self?.images.removeAll(keepingCapacity: false)
deinit {

This registers an observer for UIApplicationDidReceiveMemoryWarningNotification to execute the closure above which clears images.

All that the code needs to do is remove all objects in the cache. This will ensure that nothing is holding onto the images any more and they will be deallocated.

To test this fix, fire up Instruments again (from Xcode with ⌘I) and repeat the steps you followed previously. Don’t forget to simulate a memory warning at the end.

Note: Make sure you launch from Xcode, triggering a build, rather than just hitting the red button in Instruments, in order to make sure you’re using the latest code. You may also want to Build and Run first before Profiling, as sometimes Xcode doesn’t seem to update the build of the app in the simulator to the latest version if you just Profile.

This time the generation analysis should look like this:

You’ll notice the memory usage dropped after the memory warning. There’s still some memory growth overall, but nowhere near as much as before.

The reason there’s still some growth is really due to the system libraries, and there’s not much you can do about those. It appears that the system libraries are not freeing all of their memory, which may be by design or may be a bug. All you can do in your app is free up as much memory as possible, and you’ve already done that! :]

Well done! One more issue patched up! It must be time to ship by now! Oh, wait – there’s still the issue of the first type of leak that you haven’t yet addressed.

Strong Reference Cycles

As mentioned earlier, a strong reference cycle occurs when two objects hold strong references to each other, therefore memory can’t ever be deallocated. You can detect these cycles using the Allocations instrument in a different way.

Close Instruments and head back to Xcode. Choose Product\Profile once again, and select the Allocations template.

This time around, you won’t be using generation analysis. Instead, you’ll look at the number of objects of different types that are hanging around in memory. Click the Record button to start this run. You should already see a huge number of objects filling up the detail panel – too much to look through! To help narrow down only the objects of interest, type “Instruments” as a filter in the field in the bottom-left corner.

The two columns worth noting in Instruments are # Persistent and # Transient. The Persistent column keeps a count of the number of objects of each type that currently exist in memory. The Transient column shows the number of objects that have existed but have since been deallocated. Persistent objects are using up memory, transient objects have had their memory released.

You should see that there is a persistent instance of ViewController – that makes sense, because that’s the screen you’re currently looking at. There’s also an instance of the app’s AppDelegate.

Back to the app! Perform a search and drill into the results. Notice that a bunch of extra objects are now showing up in Instruments: the SearchResultsViewController and the ImageCache amongst others. The ViewController instance is still persistent, because it’s needed by its navigation controller. That’s fine.

Now tap the back button in the app. The SearchResultsViewController has now been popped off the navigation stack, so it should be deallocated. But it’s still showing a # Persistent count of 1 in the Allocations summary! Why is it still there?

Try performing another two searches and tap the back button after each one. There are now 3SearchResultsViewControllers?! The fact that these view controllers are hanging around in memory means that something is keeping a strong reference to them. Looks like you have a strong reference cycle!

Your main clue in this situation is that not only is the SearchResultsViewController persisting, but so are all the SearchResultsCollectionViewCells. It’s likely that the reference cycle is between these two classes.

Thankfully, the Visual Memory Debugger, introduced in Xcode 8, is a neat tool that can help you further diagnose memory leaks and retain cycles. The Visual Memory Debugger is not part of Xcode’s Instrument’s suite but is nevertheless such a useful tool that it’s worth including in this tutorial. Cross-referencing insights from both the Allocations instrument and the Visual Memory Debugger is a powerful technique that can make your debugging workflow a lot more effective.

Getting Visual

Quit the Allocations instrument and exit the Instruments suite.

Before starting the Visual Memory Debugger, enable Malloc Stack logging in the Xcode scheme editor like this: Click on the Instruments Tutorial scheme at the top of the window (next to the stop button) and select Edit Scheme. In the pop-up that appears, click the Run section and switch to the Diagnostics tab. Check the box that says Malloc Stack and select the Live Allocations Only option and click close.

Start the app directly from Xcode. Like before, perform at least 3 searches to accumulate some data.

Now activate the Visual Memory Debugger like this:

  1. Switch to the Debug navigator.
  2. Click this icon and choose View Memory Graph Hierarchy from the pop up.
  3. Click on the entry for the SearchResultsCollectionViewCell.
  4. You can click any object on the graph to view details on the inspectors pane.
  5. You can view details in this area. Switch to the Memory inspector here.

The Visual Memory Debugger pauses your app and displays a visual representation of objects in memory and the references between them.

As highlighted in the screenshot above, the Visual Memory Debugger displays the following information:

  • Heap contents (Debug navigator pane): This shows you the list of all types and instances allocated in memory at the moment your app was paused. Clicking on a type unfolds the row to show you the separate instances of the type in memory.
  • Memory graph (main window): The main window shows a visual representation of objects in memory. The arrows between objects represent the references between them (strong and weak relationships).
  • Memory Inspector (Utilities pane): This includes details such as the class name and hierarchy, and whether a reference is strong or weak.

Notice how some rows in the Debug navigator have a bracketed number next to them. The number in brackets indicates how many instances of that specific type are in memory. In the screenshot above, you can see that after a handful of searches, the Visual Memory Debugger confirms the results you saw in the Allocations instrument, i.e. anywhere from 20 to (if you scrolled to the end of the search results) 60 SearchResultsCollectionViewCell instances for every SearchResultsViewController instance are being retained in memory.

Use the arrow on the left side of the row to unfold the type and show each SearchResultsViewControllerinstance in memory. Clicking an individual instance will display that instance and any references to it in the main window.

Notice the arrows pointing to the SearchResultsViewController instance. It looks like there are a few Swift closure contexts instances with references to the same view controller instance. Looks a little suspect, doesn’t it? Let’s take a closer look. Select one of the arrows to display more information in the Utilities pane about the reference between one of these closure instances and the SearchResultsViewController.

In the Memory Inspector, you can see that the reference between the Swift closure context and the SearchResultsViewController is strong. If you select the reference between the SearchResultsCollectionViewCell and the Swift closure context, you will see that this is marked strong as well. You can also see that the closure’s name is “heartToggleHandler.” A-ha! This is declared in the SearchResultsCollectionViewCell class!

Select the instance of SearchResultsCollectionViewCell in the main window to show more information on the inspector pane.

In the backtrace, you can see that the cell instance was initialized in collectionView(_:cellForItemAt:). When you hover over this row in the backtrace, a small arrow will appear. Clicking the arrow will take you to this method in Xcode’s code editor. Neato!

In collectionView(_:cellForItemAt:), locate where each cell’s heartToggleHandler variable is set. You’ll see the following lines of code:

cell.heartToggleHandler = { isStarred in
  self.collectionView.reloadItems(at: [indexPath])

This closure handles when the heart button in a collection view cell is tapped. This is where the strong reference cycle lies, but it’s kind of hard to spot unless you’ve come across one before. But thanks to the Visual Memory Debugger, you were able to follow where the trail all the way to this piece of code!

The closure cell refers to the SearchResultsViewController using self, which creates a strong reference. The closure captures self. Swift actually forces you to explicitly use the word self in closures (whereas you can usually drop it when referring to methods and properties of the current object). This helps you be more aware of the fact you’re capturing it. The SearchResultsViewController also has a strong reference to the cells, via their collection view.

To break a strong reference cycle, you define a capture list as part of the closure’s definition. A capture list can be used to declare instances that are captured by closures as being either weak or unowned:

  • Weak should be used when the captured reference might become nil in the future. If the object they refer to is deallocated, the reference becomes nil. As such, they are optional types.
  • Unowned references should be used when the closure and the object it refers to will always have the same lifetime as one another and will be deallocated at the same time. An unowned reference can never become nil.

To fix this strong reference cycle, add a capture list to the heartToggleHandler like this:

cell.heartToggleHandler = { [weak self] isStarred in
  self?.collectionView.reloadItems(at: [indexPath])

Declaring self as weak means that the SearchResultsViewController can be deallocated even though the collection view cells hold a reference to it, as they are now just weak references. And deallocating the SearchResultsViewController will deallocate its collection view, and in turn, the cells.

From within Xcode, use ⌘+I again to build and run the app in Instruments.

Look at the app again in Instruments using the Allocations instrument as you did before (remember to filter the results down to show only the classes that are part of the starter project). Perform a search, navigate into the results, and back again. You should see that the SearchResultsViewController and its cells are now deallocated when you navigate back. They show transient instances, but no persistent ones.

XCode Instruments with Swift