Swift: Unit Testing for Retain Cycles and Memory Leaks
As you may already know, one of the biggest troubles in Swift and iOS development is retain cycles. Retain cycles are the causes of another major pain: Memory leaks. A retain cycle is caused every time 2 or more objects hold a strong reference to each other. Swift uses a mechanism called Automatic Reference Counting (ARC) in order to manage memory (allocations / deallocations etc.) . As the name suggests, in Layman’s terms ARC counts how many strong references of an object exist in memory and when the count for an object is 0, then ARC deallocates the memory it takes. Thus, when 2 or more objects hold strong references to each other, their count never becomes 0 because they retain each other in memory and as a result they are never deallocated and memory leaks are caused.
There are many ways to detect if an application or part of code may contain retain cycles that cause memory leaks. Most of them work in the runtime (Memory graph debugger, memory snapshots etc). However, we can write unit tests to ensure that a Class we have created does not contain any retain cycle that could cause an unwanted memory leak.
Retain cycles: An example.
In Swift all variables can either be strong or weak and by default variables are strong. The main difference between them is that strong variables increase the ARC memory count while weak variables do not. So let’s say that we have an object in memory that it has a count of 2, if we assign it to a strong variable, the ARC count for the object will become 3. If we assign the same object to a weak variable, the ARC count will remain 2.
Let’s see an example:
So on the code above there is a retain cycle. The reason is that car object has a strong reference to its wheels and its wheels have a strong reference to the car. This retain cycle will create a memory leak that may lead to unwanted results such as app crashes. But what if we could write a unit test that would be able to tell us if our class has a retain cycle or not? Then we would be perfectly sure that our code is safe and it will not result in memory related crashes.
Unit testing to avoid retain cycles and memory leaks
Α super simple and easy way to ensure that a class does not contain any retain cycle, is to write a unit test for this purpose.
Knowing that an object is only kept “alive” (existing in memory) for as long as there is at least one strong reference to it, which means that a weak reference to an object will be nil when the last strong reference is released, we can write a test that has 2 references to our System Under Test (aka SUT) which is our class. The one reference should be strong and the other one should be weak. So, if we deallocate the strong one, the weak must also become nil because this is exactly how the ARC mechanism works. In case the weak reference is not nil, it means that the class we test contains a retain cycle. And Voila! The unit test protected us from unsafe code and memory leaks!
The test for the Vehicle class we coded before would look like this. And it would fail as sut holds a strong reference to wheel1 and vice-versa.
In this gist there is the complete playground.
Unit tests have proved numerous times that can save us from major incidents in production and memory leaks make no exception. With the method we presented in this article, we can now be sure that any class we write will not create any retain cycle that will cause to a memory leak.