How I stopped that memory leak and reclaimed 150MB of memory — Swift

Guven James B
Swift2Go
Published in
4 min readAug 13, 2018

Memory management might not be a fun topic among iOS developers but it requires some attention if you want to take your app to the next level. Apps that leak memory do have more chance of being force-killed by iOS; providing a subpar experience for its users.

Good news is that detecting memory leaks has become easier and easier over the years. XCode provides some really good tools so that you can fix and avoid the leaks.

If you are having memory related issues in your app or just want to learn more about memory management in iOS, my top 3 resources are listed below. I have actually recently used these resources to fix a major memory leak in my app.

  1. Memory Management in Swift article by Kaira Diagne: The article does a nice introduction to the topic as well as providing an example of a memory leak fix.
  2. This excellent Stackoverflow answer that describes how to use XCode’s Allocations Instrument as well as the newer ‘Debug Memory Graph’ option. The latter is definitely a life-saver.
  3. This amazing Tweet that basically makes it fun to catch memory issues in your app. It explains that you can put a breakpoint in XCode that lets you immediately know (by simply playing a sound) that a UIViewController was released from memory. Add this breakpoint to your app from day 1 and you will for sure catch any memory leaks as they happen.

Having these tools in my arsenal, I have targeted a memory leak that have been around in my app for a while. The app is called Buluttan and it is a weather app that comes with its own forecasts and alerts. The leak occurs while displaying a view-controller that has a Map embedded. Here is how the leak happens:

Memory Leak in action!

It look pretty horrific, doesn’t it? The MapKit that gets loaded in the second step never gets removed from memory due to the memory leak. The app gets heavier with each tap and eventually gets force-removed from memory.

What happened here was that the second ViewController somehow created a strong reference cycle; causing it not to be removed from memory. Even though the user dismissed the Map in the 3rd step above, the map is still with us in memory. A strong reference cycle caused it not to be deallocated.

Figuring out where the cycle occurred is actually not that hard. The usual suspects for the strong cycle are:

  1. Strong delegates: If you are using the Delegate pattern in your code, it is highly likely that you have forgotten to define the delegate with a weak reference. The entity that will delegate the work should have its delegate defined as below:

private weak var myDelegate: ImportantDelegate? = .none

Without the weak keyword, you are introducing a strong cycle right away. This was the problem in my case as well! I have initialized my LoaderView with a strong delegate. That caused the strong cycle!

2. Strong Self in Closures: Most of the network completion handlers are passed as closures from the UI. When the network call finishes, these handlers are invoked and the UI updates itself. If your network handler entity has a strong reference to your ViewController, the VC never gets removed from memory; the network handler is still pointing at it. This can be prevented by making self a weak reference via a capture list:

myNetworkHandler.fetchSomething { [weak self] (fetchedEntities) in

guard let strongSelf = self else { return }

strongSelf.updateUI()

}

Hope this clarified how memory leaks are formed and how they can be prevented. The top 3 resources listed in beginning of the article should really help resolve any memory issues. Especially, the 3rd resource is such a great hack!

One last hint while working on memory bugs: The Memory Graph in XCode is really an excellent tool. It tells you who is still holding a strong reference to your ViewController and points to you the right direction. In my case, it let me get rid of many strong cycles but removing the last strong cycle was bit of a hassle. Here is what the Graph looked like:

XCode Memory Graph

Graph tells me that UIApplication is holding a reference to my VC in its _statusBarTintColorLockingControllers. I had no idea what this pointer was since I don’t have such a variable defined. The problem ended up being in one my Delegates and it wasn’t really related to UIApplication. So, if you are seeing this graph, try to look for the strong cycle in your code. CocoaTouch isn’t really the cause of the problem here; it is a bit misleading.

I would love to chat about iOS and programming in general on Twitter (@guventhejames)!

--

--