iosmemory-managementviewdidunloadchildviewcontroller

Set nil to child view controllers in viewDidUnload


My iOS app is navigation-based with the following structure:

@interface ViewControllerA : UIViewController
@property (strong,nonatomic) ViewControllerB *viewControllerB;
@property (strong,nonatomic) ViewControllerC *viewControllerC;
...

viewControllerB and viewControllerC get instantiated before ViewControllerA's navigationController pushes them.

In my understanding, everything that is retained in ViewControllerA should be set to nil in ViewControllerA's viewDidUnload. Should I do the same to child view controllers? Like this:

-(void)viewDidUnload
{
    self.viewControllerB=nil;
    self.viewControllerC=nil;
}

I found an issue when there was "received memory warning" initiated from viewControllerC. Afterward, viewDidUnload of the parent view controller (i.e. viewControllerA) was called, thereby setting nil to 'viewControllerB'. Unexpectedly, viewDidUnload of viewControllerB is also called. So I got "message sent to deallocated object" if I set nil to viewControllerB's subviews (in viewControllerB's viewDidUnload).

Does it mean that I should not set nil to child view controllers? What is the best practice for memory management in this situation?

P.S. I use ARC.


Solution

  • After a call to viewDidUnload, a UIViewController should maintain its state, i.e. not releasing anything that cannot be easily recreated. Usually you set to nil any data that is related to the view hierarchy, for example strong references to some subviews or custom data created in the viewDidLoad. In you example, your parent controller A is expected to be able to recover after viewDidUnload was called, meaning that in the future a call viewDidLoad will restore your controller and not crash.

    Moreover, all view controllers are registered for memory warning notifications, so when a memory warning occurs they can unload any views that isn't currently shown. I don't know if the order the view controllers are called is deterministic, for example called starting from the top view controller, so you should be cautious when setting a nested UIViewController to nil in the parent controller viewDidUnload.

    But as you must maintain the state of yours controllers after viewDidUnload, you must not set your child controllers to nil. What you must do is relinquish every strong references you have on the controller subviews (for example a strong reference to a UILabel etc.), but not relinquish the controller itself.