Dealing With Memory Warnings in View Controllers

by Ed on October 3, 2008

When the iPhone decides you are consuming too much memory (and I’m not sure what the deciding factors are), it calls your application and ultimately your view controllers’ didReceiveMemoryWarning method. In here you’re supposed to clean any caches, etc. But there’s a small wrinkle in how you need to handle this in a view controller.

Is My View Still There?

Basically, there’s a comment in the template code that says something like:

// Releases the view if it doesn't have a superview

This is on the line to [super didReceiveMemoryWarning];. Well, this news about your view being released is good, because it means that you can undo what you might have set up in viewDidLoad, your go-to place to know your view is there and ready for use. In fact, I always try to do the minimum in any init method and save it all for viewDidLoad.

The problem is that you don’t really know if it threw the view out. You can’t just test self.view as this will recreate the view! So I’ve been using a little trick to help me out:

Essentially I see if the superview was nil before calling the super method. If superview was nil, the view really does go away (unless you have a reference to it of course, in which case things get much more complicated). If the view didn’t go away, you still might want to destroy any caches anyway, as shown above.

When we know the view is gone, I usually unregister any observers I might have installed, since they tend to operate on the view, which will just bring it back into being, which is bad. If the view is hidden, why bring it back into existence if no one can see it? Plus, aren’t we trying to use less memory? It should stay dormant until it’s brought back into the visible world.

Anyway, that’s what I’ve learned to do to ensure that my view is gone and I can tear down virtually everything, as if the controller just got initialized. It sure would be nice if we had a viewWillUnload method to catch this for realz instead of what amounts to a best guess.

Other Considerations

Even if the approach above worked like a charm, you might have other reasons to keep some data around. I had a situation where I had a view pushed onto a navigation controller that referenced the view underneath. Basically an iterator of the list below it, ala Mail. So I couldn’t throw away all my data structures like I otherwise might have when the view disappeared. So I ended up employing this:

// We can only safely purge the world if we are on top.
BOOL isOnTop = ([self.navigationController topViewController] == self);

And inside the if (superview == nil) condition I also checked isOnTop. If not, I can’t destroy everything.

Keep in mind that isOnTop can be true even the view was not visible. In a tab bar it might be the top controller on the non-selected tab, for example. In any case, if you’re on top you can safely dispose of all of your data structures, as you know there’s no controllers above you.

So there’s two ways I cope with memory warnings. As mentioned, they probably won’t solve 100% of the needs out there, but it might give you some ideas for you own applications.

I should also point out that you need to check for the superview first before calling [super didReceiveMemoryWarning] because if you call self.view and it’s already been released then you will be loading that view all over again.

Also, ALL your viewControllers get this call so you do need to verify you need to tear down your custom views before doing so, otherwise you’ll end up with black screens in front of users.

This technique really isn’t robust either. The reason you can’t test your view against nil, is that self.view (or [self view]) will recreate the view if it’s nil.

Calling, self.view.superview will simply do the same thing, as it calls self.view before accessing the superview property. Even if you put the call to self.view.superview before calling [super didReceiveMemoryWarning], how do you know you won’t get another memory warning later in the run?

I don’t know that Apple guarantees this method will only be called once. The viewDidUnload callback in OS 3.0 helps, but I still don’t know of a good way to handle this in OS < 3.0.