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.

ios developer memory leak

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

iOS snapshot testing

Improving testability of CLLocationManager

How to simplify the data layer with MoyaRx and Codable 

Espresso Testing

Mobile app development trends of the year

Banco Falabella wearable case study 

Mobile development projects 

Viper architecture advantages for iOS apps 

Why Kotlin ? 

Software architecture meetups