Table of Contents
By Apple’s definition, a memory leak is: “Memory that was allocated at some point, but was never released and is no longer referenced by your app. Since there are no references to it, there’s now no way to release it and the memory can’t be used again.” To fully understand memory leaks we first must explain how the memory management works on iOS.
Automatic reference counting (ARC)
ARC works by keeping the count of the times an instance is referenced.
These references may be weak or strong, when a strong reference to an instance is declared, this counter increases by 1. When that reference is released the counter decreases by 1, hence when the counter for that instance reaches 0 ARC can safely deallocate it entirely from the memory heap because no one is using it. Weak references don’t increase the reference counter and are automatically released when the counter is 0.
Retain cycles
As a developer, this is the principal problem that we face when dealing with ARC. It happens when two objects hold strong references to each other. The first object reference count cannot be 0 until the second object is released, and at the same time, the second object reference counter cannot be 0 until the first object is released.
Here is a trivial example of a retain cycle:
class Dog {
var owner: Person?
}
class Person {
var dog: Dog?
}
let mark = Person()
let pancho = Dog()
mark.dog = pancho
pancho.owner = mark
Dog and Person have strong references to each other and will never be deallocated from memory.
Breaking a retain cycle
As we explained before, references can be strong or weak. Unlike strong references, weak references don’t increase the reference counter and therefore are optional because weak references don’t guarantee that the reference will have value at runtime access. There are two types of weak references:
Weak
This type of reference is optional and we have to check if the reference of that object is still valid when accessing it. This type of reference is used when we are not sure if the lifetime of the reference is shorter or longer than the object referencing it.
Unowned
This type of reference is not optional and we should only use it when we are sure that the lifetime of the reference is longer ( or the same) as the object referencing it. If we try to access an unowned reference which is no longer in memory we will have a runtime exception.
We could refactor the example above to break the retain cycle and avoid the memory leak like so:
class Dog {
weak var owner: Person?
}
class Person {
var dog: Dog?
}
let mark = Person()
let pancho = Dog()
mark.dog = pancho
pancho.owner = mark
Now pancho has a weak reference to the owner while mark still has a strong reference to Dog. When mark is released, pancho will automatically be released as well by ARC.
Retain cycles in closures
Closures are one of the main features of Swift but it’s very easy to create a retain cycle if not used properly. Closures are reference types and therefore can result in a memory leak if you reference any object that has a reference to the closure itself. See the example of a retain cycle below, the body of the closure is referencing the class.
class TestClass {
let constant = "Hello"
lazy var closure: (() -> ())? = {
print(self.constant)
}
}
This can be resolved using a capture list. You can define the type of reference to use inside de body closure, by marking the self reference to weak the retain cycle is broken.
lazy var closure: (() -> ())? = { [weak self] in
print(self?.constant)
}
Detecting memory leaks
The case above is quite simple and can be detected just by looking at the code but in real applications memory leaks are sometimes not that obvious. Therefore we need tools and technics to detect them. Apple introduced the Memory graph debugger in Xcode 9 which allows us to stop the app execution and get a snapshot of the current memory state. We can see a list of all the objects held in the memory heap, if we select an object we can see all the objects that are referencing it as well as a backtrace of that object.
To detect memory leaks you should run the app and navigate through all possible flows and open several times the same view controllers, then enter memory graph debugger and look at the memory heap. Look for objects that shouldn’t be in memory, for example:
- A view controller that is no longer present in the app.
- An unusual number of instances of the same object.
The memory graph debugger also shows a purple exclamation beside objects that may be leaking but you must check if that’s really a memory leak or a false positive.
Testing memory leaks
It is quite easy and straightforward to unit test memory leaks. We have to assert that the system under test (SUT) is nil after the test case has finished execution. In order to do that we can assign the SUT to a weak reference and check if this reference is nil in the teardown method.
class MemoryLeakTestTests: XCTestCase {
weak var weakSUT: Person?
func testPersonLeak() {
let mark = Person()
weakSUT = mark
let pancho = Dog()
mark.dog = pancho
pancho.owner = mark
}
override func tearDown() {
XCTAssertNil(weakSUT, “SUT is leaking”)
}
}
Using the Person – Dog example exposed before we can run the test and see that the test is failing which results in a memory leak. If we break the retain cycle by setting the property owner of the dog to weak and execute the test again we see that now the test passes, weakSUT is nil and therefore this reference has been released.
If you are interested in knowing more about Memory leaks in iOS or mobile app development in general, I highly recommend you to subscribe to our monthly newsletter by clicking here.
If you found this article about Memory leaks in iOS interesting, you might like…
iOS Objective-C app: sucessful case study
Improving testability of CLLocationManager
How to simplify the data layer with MoyaRx and Codable
Mobile app development trends of the year
Banco Falabella wearable case study
Viper architecture advantages for iOS apps
Author
-
Graduate in Audiovisual Systems Engineering focused on iOS Development. Constantly learning new technologies and work methodologies, good time management and problem solving skills.
View all posts