UIViewController Memory Management Methodson 12 April 2011

Apple stubs out methods and gives an idea of what to do when it comes to freeing up memory from UIViewControllers. The documentation goes further than the stubs, though I feel it does not go far enough.

the path to memory efficiency as represented by an iPhone background image as a path in the forest, which is convered up by the slide to unlock bar

There have been a few snags when specific methods would be called to free up memory, especially the didReceiveMemoryWarning and viewDidUnload. Most frequently the didReceiveMemoryWarning method would be called when memory runs low. The caveat is that the viewDidUnload method would not always be called afterwards. Essentially, with didReceiveMemoryWarning prepare for the worst and when viewDidUnload the worst has arrived.

Providing a consistent experience is important after viewDidUnload has been called. Apple makes no mention of it, however I believe that the view should be recreated as consistently as possible. Often memory has run low in an iOS app after a few UIViewControllers have been pushed to a UINavigationController. When returning to the root, the original controller has no selection because of low memory on the device. The following is merely an example of how to provide that consistent experience in a low memory situation.

didReceiveMemoryWarning

This method is called, unsurprisingly, when a view controller receives a memory warning. Based solely on my experience, this is where simple storage of objects to rebuild the view should be stored, while removing the non–essential objects from the view and memory to mitigate the second level of memory warnings. Because didReceiveMemoryWarning has been called, it does not mean viewDidUnload will be. The following image from the Apple documentation is a misnomer, because of that reason.

Apple’s diagram of the view unloading process

An example would be a view controller with a few table views. Perhaps these tables select data to send to a different (or modal) view controller to perform some other task. Would it not be great to come back to this view controller and have the original data still selected? If the app needs memory, then why save the indexPaths and set the table views to nil and release the corresponding data. This will free up memory and at the same time help to reload the view controller when necessary.

- (void)didReceiveMemoryWarning {
  if(self.modalViewController != nil) { //checks for existence of another view
    [self setSelectedData: [self.dataTableView indexPathForSelectedRow] ];
    [self.dataTableView removeFromSuperview];
    [self setDataTableView: nil ];
   
    [self setSelectedAuxiliaryData: [self.auxiliaryDataTableView indexPathForSelectedRow] ];
    [self.auxiliaryDataTableView removeFromSuperview];
    [self setAuxiliaryDataTableView: nil ];
  }
  [super didReceiveMemoryWarning];
  // Release any cached data, images, etc. that aren't in use.
}
 

The example above is saving the current indexPath for the dataViewTable and the auxiliaryViewTable to selectedData and selectedAuxiliaryData respectively. This will allow us to keep those selections for display later.

The main purpose for didReceiveMemoryWarning is to release anything that can quickly be recreated. This is also to avoid the next level of memory warnings, which will call viewDidUnload of your UIViewController which will release everything in your view (assuming it has been set up that way).

viewDidUnload

If your app has gotten the memory warning and viewDidUnload has been called, it is really hurting for memory. The world of mobile devices, it is expected to happen. This method will be called because the view property on your view controller has been set to nil. viewDidUnload is the place to set all of the object created in the view controller to nil.

The controller will have already called the didReceiveMemoryWarning method as described above if viewDidUnload is called.

- (void)viewDidUnload {
  self.auxiliaryDataTableView = nil;
  self.dataTableView = nil;
  [super viewDidUnload];
}
 

Be sure to set any properties on your views that might be retained to nil before setting the object itself to nil. Notice the selected indexPaths that were set in didReceiveMemoryWarning are intentionally not being set to nil. Child view controllers that may need a reference back to the parent (that are not handled by delegation) should be set to nil first. Otherwise the objects will be held in memory and will defeat the purpose of the viewDidUnload method.

It will come as no surprise that once the view is released it will need to be recreated when needed. The case that the view controller was loaded from a nib it would be initWithNibName:bundle: otherwise loadView is used to create the entire view in the view controller programmatically.

viewDidLoad

This method is called after the view is loaded for the first time and also every time the view has been unloaded. The saved indexPaths that were saved from the example code above will now be set in viewDidLoad.

- (void)viewDidLoad {
  [super viewDidLoad];
  [self.dataTableView selectRowAtIndexPath: self.selectedData animated: NO scrollPosition: UITableViewScrollPositionMiddle ];
  [self.auxiliaryDataTableView selectRowAtIndexPath: self.selectedAuxiliaryData animated: NO scrollPosition: UITableViewScrollPositionMiddle ];
  //setting the indexPaths to nil, no longer needed
  self.dataTableView = nil;
  self.selectedAuxiliaryData = nil;
}
 

The view has been recreated and then we can set the indexPath of the two table views back to the original values before it was wiped out. Also, via the selectRowAtIndexPath:animated:scrollPosition: method, the table view can be scrolled to ensure the selected row is in view by scrolling it to the middle of the table.

If you enjoyed this, use this shorter link to share: http://the.ichibod.com/s/vcmmm