In this article, I discuss issues related to how view controllers are displayed and managed, and solutions to (hopefully) make your life easier as an iOS programmer.
View controller containersView controller containers are objects whose responsibility is to manage some set of view controllers, displaying or hiding them as required. Often, containers are view controllers themselves (UITabBarController, UINavigationController or UISplitViewController, for example), but this is is by no means required (UIPopoverController, for example, directly inherits from NSObject).
Writing custom view controllers is not an easy task and requires an entire dedicated post. I here only briefly mention two important properties that well-written view controller containers must exhibit:
UIKit view controller containers of course satisfy the above requirements. Be especially careful when using containers from other sources, though. In particular check that the above two properties are satisfied. If this is not the case, I strongly advise you not to use them.
Displaying view controllersNo matter what you intend to do, any application you write most probably begins with a root view controller. This is the view controller whose view is directly added as first subview of the main window. This view controller is not retained anywhere, you must therefore do it yourself (e.g. in your application delegate). Apple engineers took care of correctly sending lifecycle and rotation events to the root view controller of an application (otherwise we would obviously have had a problem). This view controller is therefore the starting point of your view controller hierarchy.
When displaying a non-root view controller, several options are available:
The third option is extremely dangerous and must be avoided, except if you are a container implementer. You namely become entirely responsible of forwarding lifecycle and rotation events correctly, which is very difficult to get right. You definitely do not want to face such issues if you are not implementing library or framework components. Of course, you can still get your application to work this way, but the code is likely to get ugly or to be a nightmare to understand. Usually, you namely end up putting all stuff into the viewDidLoad method since the other lifecycle methods cannot be trusted. That's bad. Don't do it. Period.
The fourth option is tempting when you need to display a view controller quasi-modally (i.e. with the covered view controller still visible). It is dangerous as well, though: Rotation events are sent correctly to the first subview of the window, but not to subsequent ones. You therefore need to find some way to get these events to be sent to view controllers you display quasi-modally, otherwise they will not rotate properly. Moreover, you need to retain the view controller somewhere (as for the root view controller, most probably in your application delegate).
You should therefore always use well-written view controller containers or +[UIViewController presentModalViewController:animated:] to display view controllers. This guarantees that you can trust view controller lifecycle and rotation events to occur as expected. The only exception to this rule is the root view controller of your application, which you directly add as first subview of your application window.
You must retain the root view controller of your application, but what about the other view controllers in your application? Should you retain them as well? Let us discuss this issue now.
Managing view controllersFor convenience, we often need to keep references to view controllers. Since the first view controller of an application has to be retained, it can be pretty tempting to think this rule can be applied everywhere as well. This is especially appealing for novice iOS programmers since it makes them confident they will never access dead view controllers.
But this is wrong. Keeping strong references to view controllers should be the exception, not the rule. Retaining view controllers where not necessary is most of the time not only useless, but also a serious waste of resources. While this can make sense in very special cases I will discuss below, if you need to keep a reference to a view controller (except the root one), a weak one suffices. As said in the previous section, you should namely always present view controllers modally or using well-behaved containers, and in all such cases the view controller you display will be retained for you. Over-retaining a view controller (and its associated view) is the easiest way to keep it in memory even if you are not using it anymore.
There is only one reason you may sometimes need to keep several additional strong references to view controllers: Caching. A container probably releases a view controller it manages when it is done with it, but sometimes you might be pretty confident that the view controller will be used again very soon. In such cases, and especially if the associated view is costly to create, it might make sense to keep the view controller alive even after the container is done with it.
Once you introduce such a cache mechanism, though, you become responsible of releasing these cached objects when memory gets critically low. If the view controllers strong references are kept by another "owner" view controller, you should take the following measures:
If the view controllers are not cached by a view controller, listen to memory warning notifications (UIApplicationDidReceiveMemoryWarningNotification) and, in your notification callback, proceed as for didReceiveMemoryWarning above.
ConclusionAlways use high-quality view controller containers or +[UIViewController presentModalViewController:animated:] to display view controllers in your application. If you need to keep a reference to a view controller somewhere, use a weak reference, except if you really want it to stay alive longer for caching purposes. In such cases, be sure to correctly respond to low-memory conditions.
There is one exception to these rules: The first view controller of your application, which you directly add as first window subview, and which you must retain.
In an upcoming article, I will thoroughly discuss the problems we face when implementing a view custom controller container. Stay tuned.