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:
Now, if the SecondViewController is dismissed, it looks like this:
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:
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