The View Controller Lifecycle and Low-Memory Warnings

A view controller, like any other object, is created through alloc and init. It does not, however, create its view at that time. Instead, it waits until the view is really needed before it calls loadView. This lazy creation of the view is good: for example, if you have a tab view with a dozen view controllers, the view for any particular view controller will only be created if that particular tab is selected. You can see this behavior in the console when you build and run HypnoTime – you will only see the log message indicating that the CurrentTimeViewController’s view is loaded after you switch to the Time tab for the first time.

How does a view controller know when to load its view? When it is sent the message view. The implementation of this method in UIViewController looks something like this:

-​ ​(​U​I​V​i​e​w​ ​*​)​v​i​e​w​
{​
 ​ ​ ​ ​i​f​ ​(​[​s​e​l​f​ ​i​s​V​i​e​w​L​o​a​d​e​d​]​ ​=​=​ ​N​O​)​
 ​ ​ ​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​/​/​ ​I​f​ ​I​ ​d​o​n​'​t​ ​c​u​r​r​e​n​t​l​y​ ​h​a​v​e​ ​a​ ​v​i​e​w​,​ ​t​h​e​n​ ​c​r​e​a​t​e​ ​i​t​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​l​o​a​d​V​i​e​w​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​s​e​l​f​ ​v​i​e​w​D​i​d​L​o​a​d​]​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​/​/​ ​T​h​e​ ​v​i​e​w​ ​i​s​ ​d​e​f​i​n​i​t​e​l​y​ ​g​o​i​n​g​ ​t​o​ ​e​x​i​s​t​ ​h​e​r​e​,​ ​s​o​ ​r​e​t​u​r​n​ ​i​t​
 ​ ​ ​ ​r​e​t​u​r​n​ ​v​i​e​w​;​
}​

This code says that anytime an object asks a view controller for its view, it will create it if it doesn’t already exist. This is exactly what happens when the user selects a tab from the tab bar: the UITabBarController sends the message view to the associated view controller and then places the returned view in the view hierarchy. Imagine what would happen if you were to send the message view to CurrentTimeViewController in its init method. In CurrentTimeViewController.m, access the view in init and change its background color to yellow:

-​ ​(​i​d​)​i​n​i​t​
{​
 ​ ​ ​ ​s​e​l​f​ ​=​ ​[​s​u​p​e​r​ ​i​n​i​t​W​i​t​h​N​i​b​N​a​m​e​:​@​"​C​u​r​r​e​n​t​T​i​m​e​V​i​e​w​C​o​n​t​r​o​l​l​e​r​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​b​u​n​d​l​e​:​n​i​l​]​;​

 ​ ​ ​ ​i​f​ ​(​s​e​l​f​)​ ​{​
 ​ ​ ​ ​ ​ ​ ​ ​U​I​T​a​b​B​a​r​I​t​e​m​ ​*​t​b​i​ ​=​ ​[​s​e​l​f​ ​t​a​b​B​a​r​I​t​e​m​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​t​b​i​ ​s​e​t​T​i​t​l​e​:​@​"​T​i​m​e​"​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​U​I​I​m​a​g​e​ ​*​i​ ​=​ ​[​U​I​I​m​a​g​e​ ​i​m​a​g​e​N​a​m​e​d​:​@​"​T​i​m​e​.​p​n​g​"​]​;​
 ​ ​ ​ ​ ​ ​ ​ ​[​t​b​i​ ​s​e​t​I​m​a​g​e​:​i​]​;​

 ​ ​ ​ ​ ​ ​ ​ ​[​[​s​e​l​f​ ​v​i​e​w​]​ ​s​e​t​B​a​c​k​g​r​o​u​n​d​C​o​l​o​r​:​[​U​I​C​o​l​o​r​ ​y​e​l​l​o​w​C​o​l​o​r​]​]​;​
 ​ ​ ​ ​}​

 ​ ​ ​ ​r​e​t​u​r​n​ ​s​e​l​f​;​
}​

Build and run the application. Switch to the Time screen and notice that it is yellow. More importantly, notice that the console says the CurrentTimeViewController’s view is loaded as soon as you start the application. By accessing the view in a view controller’s initialization method, the view is no longer loaded lazily. This doesn’t seem like that big of an issue; that is, until you factor in low-memory warnings.

When the system is running low on RAM, it issues a low-memory warning to the running application. The application responds by freeing up any resources that it doesn’t need at the moment and can easily recreate. View controllers, during a low-memory warning, are sent the message didReceiveMemoryWarning. The default implementation of this method will check to see if the view is currently on screen; if it is not, it is released. (If the view is on screen, nothing happens.)

Run the application on the simulator. Switch to the Time tab, notice the view is yellow, and then switch back to the Hypno tab. From the Hardware menu in the simulator, select Simulate Memory Warning. This will issue a low-memory warning to your application. Then, switch back to the Time tab: the view is no longer yellow.

Why isn’t the view yellow anymore? When a low-memory warning occurs, CurrentTimeViewController’s view is destroyed – but the instance of CurrentTimeViewController is not. When you switch back to the Time tab, the view is recreated, but the CurrentTimeViewController itself is not recreated. Thus, the message init, which sets the background color of the view to yellow, is never sent to the instance of CurrentTimeViewController again.

We can make a rule out of this: never access a view controller’s view in that view controller’s initialization method. If you have extra work you want to perform on the view, do so in viewDidLoad. This message is sent to a view controller each time it loads its view. Delete the line of code that sets the background color of the view to yellow from the init method and add it to viewDidLoad in CurrentTimeViewController.m.

-​ ​(​v​o​i​d​)​v​i​e​w​D​i​d​L​o​a​d​
{​
 ​ ​ ​ ​[​s​u​p​e​r​ ​v​i​e​w​D​i​d​L​o​a​d​]​;​

 ​ ​ ​ ​N​S​L​o​g​(​@​"​L​o​a​d​e​d​ ​t​h​e​ ​v​i​e​w​ ​f​o​r​ ​C​u​r​r​e​n​t​T​i​m​e​V​i​e​w​C​o​n​t​r​o​l​l​e​r​"​)​;​

 ​ ​ ​ ​[​[​s​e​l​f​ ​v​i​e​w​]​ ​s​e​t​B​a​c​k​g​r​o​u​n​d​C​o​l​o​r​:​[​U​I​C​o​l​o​r​ ​y​e​l​l​o​w​C​o​l​o​r​]​]​;​
}​

Build and run the application. You can simulate as many memory warnings as you want and the view will always be yellow. This is the desired behavior for handling low-memory warnings – the user should never know that one occurred.

Figure 7.15  Retain count of views

Retain count of views

In addition to viewDidLoad, you may also have to implement its counterpart, viewDidUnload. A view controller’s view can have subviews and some of these subviews will be connected as outlets from the view controller. When an object is connected via an outlet, it is retained by the object that has the outlet to it. For example, the timeLabel is an outlet of CurrentTimeViewController. Therefore, CurrentTimeViewController retains its timeLabel. A view is also retained by its superview, so timeLabel has a retain count of two.

When a low-memory warning occurs, the view of the CurrentTimeViewController will be deallocated (its retain count will only be one, since CurrentTimeViewController is the only thing that retains it when it is not on the screen). When this view is deallocated, it will send release to its subviews. The timeLabel then has a retain count of one (from the CurrentTimeViewController), but it doesn’t have a superview any longer. When CurrentTimeViewController reloads its view, a new UILabel instance is created from the XIB file.

Therefore, as soon as a view controller’s view is unloaded, you should release all outlets since they will eventually be replaced by a new object. Do so by implementing viewDidUnload in CurrentTimeViewController.m.

-​ ​(​v​o​i​d​)​v​i​e​w​D​i​d​U​n​l​o​a​d​
{​
 ​ ​ ​ ​N​S​L​o​g​(​@​"​C​u​r​r​e​n​t​T​i​m​e​V​i​e​w​C​o​n​t​r​o​l​l​e​r​'​s​ ​v​i​e​w​ ​w​a​s​ ​u​n​l​o​a​d​e​d​ ​d​u​e​ ​t​o​ ​m​e​m​o​r​y​ ​w​a​r​n​i​n​g​"​)​;​
 ​ ​ ​ ​[​s​u​p​e​r​ ​v​i​e​w​D​i​d​U​n​l​o​a​d​]​;​
 ​ ​ ​ ​[​t​i​m​e​L​a​b​e​l​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​t​i​m​e​L​a​b​e​l​ ​=​ ​n​i​l​;​
}​

Notice that we did not release the button, which is also a subview of the CurrentTimeViewController’s view. This is because we did not have an outlet to this object.

When a view controller is deallocated, it is not sent the message viewDidUnload. (Its view is still released, though.) Therefore, a view controller that will get deallocated must release its outlets in dealloc. Even though CurrentTimeViewController will not be released in this application, implement its dealloc in CurrentTimeViewController.m.

-​ ​(​v​o​i​d​)​d​e​a​l​l​o​c​
{​
 ​ ​ ​ ​[​t​i​m​e​L​a​b​e​l​ ​r​e​l​e​a​s​e​]​;​
 ​ ​ ​ ​[​s​u​p​e​r​ ​d​e​a​l​l​o​c​]​;​
}​

The biggest thing to keep in mind is that the view and its view controller are separate objects. A view controller will see its view created, moved on screen, moved off screen, destroyed and created again – perhaps many times over. You can think of the view as a renewable resource that the view controller uses to communicate with the user. Figure 7.16 shows the life cycle of a view controller’s view in full.

Figure 7.16  Lifecycle of a view controller

Lifecycle of a view controller
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.178.181