Question

Background:

I have a tab bar application. Each tab contains navigation controller allowing the user to transition from one view to the other showing a drill down information of the data (each view is being handled by a view controller and each view controller class has didReceiveMemoryWarning method). Lists are populated by pulling the data from web services.

Problem:

When i use "Hardware > Simulate Memory Warning" option of iPhone Simulator, the didReceiveMemoryWarning method is called for ALL my view controllers - even the one which the user is viewing. I don't want to clear any content which is being used by the active view controller. How can I achieve that?

Which method should have the implementation to reload the data after the data was released because of memory warning? (I see that the view controller classes that contain a table view call viewDidLoad method when user comes back to that view, but if the view contains (say UIWebView) then viewDidLoad method is not called. Why is that?)

Edited (Friday 30 January 2009 - 03:10 PM)

(Note: I'm using Interface builder for creating views, and loadView method is commented out.)

So, when a view controller receives a memory warning message, these are the steps that are carried out:

  1. Following method is called:

    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning]; 
    }
    
  2. As a result of call to [super didReceiveMemoryWarning], [self setView:nil] gets automatically called?

  3. If any resources should be cleared, then setView method should be overwritten to clear local resources.

  4. [self setView:nil] is not called if the view is currently active (By default). Right? - I'm really curious which method takes this decision and how?

Can you please confirm. Plus, I was getting an error following this approach but adding myObject = nil after releasing myObject in dealloc method of controller class fixed the issue. Thanks.

Was it helpful?

Solution

This is an old question, but I don't see a proper answer, so here goes:

When a memory warning is received, -didReceiveMemoryWarning gets called in ALL view controllers, whether they are the "current" one or not. The view controllers are simply listening for the memory warning event broadcast.

If the view controller's view isn't being used at the time of the memory warning, the controller will unload it by setting the property to nil. How does it know if the the view is used? By the view's -superview property. If view.superview is nil, the view isn't part of any tree and can be unloaded safely.

Once that happens, the controller's -viewDidUnload gets called. This is the correct place to unload any outlets, and anything that will get re-created in -viewDidLoad.


So what is -didReceiveMemoryWarning for? Your controller might have objects that don't get instanced until accessed. For example, you could have a controller that sometimes needs a big chunk of data from a file, but not always. You could have a property set for it like this:

- (NSData*)bigChunkOfData {
  // Get data from our instance variable _data, read from disk if necessary
  if (_data == nil) {
    _data = [[NSData alloc] initWithContentsOfFile:@"/path/to/data"];
  }
  return _data;
}

This will read the data from disk this first time, then keep it in an instance variable. Since the _data variable is created on demand, it's safe for us to unloaded it in low-memory situations: it'll just get created again next time we need it.

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];

  [_data release];
  _data = nil;  // <-- Very important: don't leave strong references dangling.
}

OTHER TIPS

I do my clean up like this:

-(void)setView:(UIView*)view
{
    [super setView:view];
    if(view == nil)
    {
       // Our view has been cleared, therefore we should clean up everything 
       // we are not currently using
....

setView:nil is called by UIViewController in response to a memory warning, if that view is not currently visible - which is basically what you want to know.

EDITED

In answer to the follow ups:

  1. Correct.
  2. That's what I do, and it works for me.
  3. Correct. The implementation of didReceiveMemoryWarning in UIViewController is what does this. If you don't override didReceiveMemoryWarning, then the base class implementation in UIViewController will be called - if you do override it, obviously you should call:

    [super didReceiveMemoryWarning]
    

To ensure that I dont have to handle this for every single viewcontroller I write.. I have just made a Xcode ViewController template which provides guidelines on which objects to release and when..

more explanation here http://iphone2020.wordpress.com/2010/05/30/efficient-memory-handling-in-uiviewcontroller-part-1/

Hope it finds useful.

In regard to the view management and memory warnings:

UIKit doesn’t only allow navigation back from a view controller, but also allows navigation to other view controllers from existing ones. In such a case, a new UIViewController will be allocated, and then loaded into view. The old view controller will go off-screen and becomes inactive, but still owns many objects – some in custom properties and variables and others in the view property/hierarchy. And so does the new visible view controller, in regard to its view objects.

Due to the limited amount of memory of mobile devices, owning the two sets of objects – one in the off-screen view controller and another in the on-screen view controller – might be too much to handle. If UIKit deems it necessary, it can reclaim some of the off-screen view controller’s memory, which is not shown anyway; UIKit knows which view controller is on-screen and which is off-screen, as after all, it is the one managing them (when you call presentModalViewController:animated: or dismissModalViewControllerAnimated:). So, every time it feels pressured, UIKit generates a memory warning, which unloads and releases your off-screen view from the view hierarchy, then call your custom viewDidUnload method for you to do the same for your properties and variables. UIKit releases self.view automatically, allowing us then to manually release our variables and properties in our viewDidUnload code. It does so for all off-screen view controllers.

When the system is running out of memory, it fires a didReceiveMemoryWarning. Off-screen views will be reclaimed and released upon memory warning, but your on-screen view will not get released – it is visible and needed. In case your class owns a lot of memory, such as caches, images, or the like, didReceiveMemoryWarning is where you should purge them, even if they are on-screen; otherwise, your app might be terminated for glutting system resources. You need to override this method to make sure you clean up your memory; just remember you call [super didReceiveMemoryWarning];.

An even more elaborate explanation is available here: http://myok12.wordpress.com/2010/11/30/custom-uiviewcontrollers-their-views-and-their-memory-management/

Fortunately, the simulator has a handy function that allows you to put low-memory situations to the test. Put some NSLog() statements in both viewDidLoad and didReceiveMemoryWarning, like this: 

- (void)viewDidLoad {
    NSLog(@"viewDidLoad"); 
    ...
}

- (void)didReceiveMemoryWarning {
    NSLog(@"didReceiveMemoryWarning");
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top