11

In iOS 6, viewWillUnload and viewDidUnload are deprecated and UIViewControllers no longer unload views that are not visible on screen during a memory warning. The View Controller Programming Guide has an example of how to manually restore this behavior.

Here is the code sample:

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Add code to clean up any of your own resources that are no longer necessary.
    if ([self.view window] == nil)
    {
        // Add code to preserve data stored in the views that might be
        // needed later.

        // Add code to clean up other strong references to the view in
        // the view hierarchy.
        self.view = nil;
    }
}

Below the code sample is the following note:

The next time the view property is accessed, the view is reloaded exactly as it was the first time.

There is an obvious flaw here. If a view controller that has not loaded its view receives a memory warning it will load its view in the line if ([self.view window] == nil) and then proceed to clean up and release it again. At best, this is inefficient. At worst, it makes the memory conditions worse if a complex view hierarchy and supporting data are loaded. I verified this behavior in the iOS simulator.

I can certainly code around this but seems odd for Apple docs to have such an error. Am I missing something?

XJones
  • 21,959
  • 10
  • 67
  • 82

1 Answers1

17

The correct check in a view controller for the view being loaded and on screen is:

if ([self isViewLoaded] && [self.view window] == nil)

My full solution in iOS 6 to have a view controller unload views and cleanup similar to iOS 5 is the following:

// will not be called in iOS 6, see iOS docs
- (void)viewWillUnload
{
  [super viewWillUnload];
  [self my_viewWillUnload];
}

// will not be called in iOS 6, see iOS docs
- (void)viewDidUnload
{
  [super viewDidUnload];
  [self my_viewDidUnload];
}

// in iOS 6, view is no longer unloaded so do it manually
- (void)didReceiveMemoryWarning
{
  [super didReceiveMemoryWarning];
  if ([self isViewLoaded] && [self.view window] == nil) {
    [self my_viewWillUnload];
    self.view = nil;
    [self my_viewDidUnload];
  }
}

- (void)my_viewWillUnload
{
  // prepare to unload view
}

- (void)my_viewDidUnload
{
  // the view is unloaded, clean up as normal
}
XJones
  • 21,959
  • 10
  • 67
  • 82
  • Are `viewDidUnload` and `viewWillUnload` there for backwards compatibility? If so, I would suggest commenting the code up to say that these two methods won't be called at all in iOS 6 (as the [UIViewController docs](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/DeprecationAppendix/AppendixADeprecatedAPI.html#//apple_ref/occ/instm/UIViewController/viewWillUnload) state). –  Oct 10 '12 at 13:43
  • Wouldn't it be *somewhat* better if could you call `[self viewWillUnload]` and `[self viewDidUnload]` accordingly in `- didReceiveMemoryWarning` instead of custom `- my_view{Will|Did}Unload`? – Ali Nov 06 '12 at 08:40
  • 3
    It may be ok. B/c these methods are called by iOS < 6.0 I wanted to my changes to be cleanly separated from the internal implementation as much as possible so the code path was clear in any OS version. Also, once I move to only support iOS 6+ it's simpler to just remove `viewWillUnload` and `viewDidUnload` completely. The `my_XXX` naming convention is arbitrary and you can name these whatever you like (of course). – XJones Nov 06 '12 at 17:10
  • 1
    @XJones, in iOS5, it seems that my_viewWillUnload and my_viewDidUnload will be called twice – LiangWang Feb 04 '13 at 00:01
  • 1
    Not in my testing. In iOS5 `viewWillUnload` and `viewDidUnload` are called first and once the view is unloaded, the `my_viewXXXUnload` versions are not called b/c the conditions in the `if` clause fail. – XJones Feb 04 '13 at 21:17