Ghostboard pixel

A Trick To Discover Retain Cycles

A Trick To Discover Retain Cycles

Although ARC does most of the memory handling work for you, your app can still suffer from so-called retain cycles. So it is very important to discover them.

ARC And Memory Handling

With the introduction of the Automatic Reference Counting (ARC) in iOS 5, memory handling has became much simpler. But ARC can not handle all scenarios, so it is still important for you to take care of the memory handling of your apps. For example, it is possible to have so-called retain cycles. So it could happen that for example your view controllers are not deallocated although there are no accessible reference to them in the app anymore. If this happens, the memory usage of the app is increasing every time the view controller is presented. And if this happens too often, the app will be closed by the operating system – the app crashes.

Retain Cycles

Let’s create an example with a retain cycle: First we are creating a RootViewController and a SecondViewController. The SecondViewController gets presented, if a button on the RootViewController is pressed. You can create this easily by using a segue in the storyboard of the app. Furthermore, there is a class called ModelObject, which has a delegate object of type ModelObjectDelegate. If the SecondViewController‘s view is loaded, the controller sets itself as the delegate of the ModelObject:

import Foundation

protocol ModelObjectDelegate: class {
    
}

class ModelObject {
    
    var delegate: ModelObjectDelegate?
       
}
import UIKit

class SecondViewController: UIViewController, ModelObjectDelegate {
    
    var modelObject: ModelObject?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        modelObject = ModelObject()
        modelObject!.delegate = self
    }
        
    @IBAction func closeButtonPressed(sender: UIButton) {
        dismissViewControllerAnimated(true, completion: nil)
    }
    
}

Ok, now let’s examine the memory handling: If we are dismissing the SecondViewController, the amount of used memory is not decreasing. But why is this? We would expect that with the dismiss of the SecondViewController memory gets deallocated. Let’s take a look at the objects. If the SecondViewController is loaded, it looks like this:

retainc1

Now, if the SecondViewController is dismissed, it looks like this:

retain2c

The RootViewController doesn’t have a strong reference to the SecondViewController anymore. However, the SecondViewController and the ModelObject have strong references to each other. And because of this they are not deallocated.

The Trick 

The trick to discover these kind of problems is the following: If an object gets deallocated, the deinit method will be called. So just insert a log message in the deinit methods of your objects:

import UIKit

class SecondViewController: UIViewController, ModelObjectDelegate {
    
    var modelObject: ModelObject?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        modelObject = ModelObject()
        modelObject!.delegate = self
    }
    
    @IBAction func closeButtonPressed(sender: UIButton) {
        dismissViewControllerAnimated(true, completion: nil)
    }
    
    deinit {
        print("SecondViewController deinit")
    }
}
import Foundation

protocol ModelObjectDelegate: class {
    
}

class ModelObject {
    
    var delegate: ModelObjectDelegate?
    
    deinit {
        print("ModelObject deinit")
    }
    
}

If we dismiss the SecondViewController, there are no log messages in the debugger! That means they are not deallocated, so something is going wrong.

The Solution

We know already that there is one strong reference too much. So let’s change delegate to a weak property:

import Foundation

protocol ModelObjectDelegate: class {
    
}

class ModelObject {
    
    weak var delegate: ModelObjectDelegate?
    
    deinit {
        print("ModelObject deinit")
    }
    
}

The object graph looks now like this:

retainc3

Because there is just one strong reference between the SecondViewController and the ModelObject, we expect that there will be no problems anymore.

And indeed, the following log messages appear in the debugger, if we dismiss the SecondViewController:

SecondViewController deinit
ModelObject deinit

This is the behaviour we’ve expected.

[thrive_text_block color=”blue” headline=”Conclusion”]Although it’s a little bit of work, you should insert log messages in the deinit methods of your view controllers in order to discover retain cycles. You could also use Instruments to discover retain cycles, but if you always place the log message in the deinit method, you can observe the deallocating behaviour constantly.[/thrive_text_block]

References

Image: @ Lightspring / shutterstock.com