3 Different Techniques to Find Memory Leaks in iOS
Hunt memory leaks in iOS like a pro!
Introduction
When developing for iOS or any other platform, it is very important to have in mind that your customer expectations are very high for your application to perform very well. They shouldn’t worry if the app responds slowly, the interface is sluggish, or any other kind of problem.
Imagine that your customers are paying their bills and suddenly the app crashes. That is not the experience they are expecting and in this case, it’s even worse because they couldn’t pay their bills.
That’s why when we are dealing with memory management, it is very important to be careful with memory leaks.
Memory Leaks in iOS
Memory leaks are very related to how swift deals with memory management and it occurs when a determined object in memory could not be recovered by the Automatic Reference Counting (ARC). The ARC is responsible for tracking and managing your app’s memory usage and also freeing up the memory used by class instances when those instances are no longer needed.
A retaining cycle is one of the most common problems that can cause memory leaks in your apps. Every variable that you create has a strong reference by default. When you create a variable inside a class referencing another class, you have a strong relationship between the two classes.
The following example illustrates the idea:
Every time we create instances of those classes we are printing the objects that were initialized and also when they were deallocated from memory.
When we create a relationship between those objects and try to set those objects to nil
, we don’t see the deinit
print.
How to fix it?
We can break this strong retain cycle by marking one of the properties as weak or unowned.
The instances can then refer to each other without creating a strong reference cycle. As compared to strong
, weak
does not increment the reference count and ARC can release the objects from memory.
Monitoring memory leaks
We fixed the problem with objects having a bidirectional relationship, but there is another common way to create memory leaks and it is when we are using closures. Closure captures variables and constants from its surrounding scope. This creates a strong reference to it and the value needed by the closure.
It’s quite possible to have thousands of closures in our project and checking every one of them for memory problems could be very difficult. Xcode has a feature that allows us to monitor for memory leaks, all we need is to open Instruments e look for the option Leaks
After that, we select the simulator we are testing on and the application target.
For our example let’s take a look at the code below:
When we run the instruments we can see that we have a memory leak and the objects with the leak.
Checking our code we notice that our client and server have a strong relationship with each other.
We can fix the memory leak and change the property to weak
or unowned
:
Running again the instruments, we can see that there are no memory leaks and all deinit
messages are being printed in the console.
SecondViewController object was deallocated
Server object was deallocated
Client object was deallocated
Another tip is to go to the debug navigator and check the memory used. When there is a leak, the memory is not deallocated causing the memory usage to increase over time.
Continuous monitoring of memory leaks
Xcode Instruments is a great tool for finding potential memory leaks but sometimes it’s not easy to identify the problem. We also need to run it every time to check for a potential leak and monitor the memory usage.
We can use another technique to continuously monitor for potential memory leaks. Xcode allows us to create a symbolic breakpoint, in this case, we can create a breakpoint to listen to the view controller’s dealloc
method and check if the view controller was deallocated from memory.
Unit tests
We can also detect potential memory leaks by writing unit tests for our classes. This is by far the more scalable way to prevent any potential memory leaks.
After fixing the retaining cycle between the objects, we run the tests again:
Finally, we can write an extension on the XCTestCase and use it to test memory leaks on every object.
Final recommendations
- A great rule of thumb is to always use
weak
orunowned
when dealing with closures or delegates. - Create a strong code style for your project, this way the absence of a weak self will be noticeable.
- Use some type of linter, like SwiftLint. This is a great tool that enforces you to adhere to a code style. You can detect issues at compile time and it helps automate code style for your team.