📜 ⬆️ ⬇️

The life cycle of UIViewController

Most iOS applications use UIViewController 's in some way or another. Where UIKit framework is, UIViewController ’s is there. There are many of them, they are everywhere, they are sitting in ambush and looking out from behind every corner. Therefore, any programmer under iOS - whether he is a green novice, barely stepping on the programming path, or a seasoned professional, should be aware of everything about UIViewController .

The reason for writing this post is that, as it turned out, you can quietly program for iOS for six months, and not fully know about the life cycle of UIViewcontroller 's. And on small projects it even turns out. However, when you have to deal with a serious, fairly large project, there are certain problems with a lack of memory, “incorrect” and “incomprehensible” work of controllers, data loss, and many more typical problems, which will be described below.

So here. In this post, I will once again talk about the life cycle of UIViewController 's, tell you about what to do and where, and in what case. The post is focused on developers of different levels, so that someone will find out something new for themselves, and someone will find a reason to otpinit moments that Juniors should pay attention to in the team.
')
Anyone interested, please under the cut

I must say right away that much of what will be described here can be found in the documentation , in various types of sources . As they say, he who seeks will always find, study, encroach, and write about his vision of the problem. In the post I will often refer to the documentation, especially in those moments that this post does not affect, but which is also desirable to know. For example, this post does not tell about changes in the orientation of UIView during device flips , but those interested in this particular issue can read about it themselves.

About UIViewController


It is a controller according to the MVC design pattern . Provides interconnection between model and display. In iOS, tasks are assigned to it related to the control of the view life cycle and displaying a UIView in various device orientations. General information can be found in the same documentation .

The life cycle of UIViewController and its UIView


Let us concentrate on the methods that are responsible for the life cycle of UIViewControllera :

Creature

Create view

Handling view state changes

Processing memory warning

Destruction

Some methods were immediately in two sections, they will be discussed separately. Another couple of methods relate to creating a UIView . They will also be given a little attention.

And now about everything in order.

Creature


To create a controller, or rather to initialize it, there are two main methods - init and initWithNibName: (NSString *) nibNameOrNil . In fact, the init method will call initWithNibName: so that only it can be considered.

The logic of the method is quite simple - we either find the .xib (.nib) file and associate it with the UIViewController , or do not associate it. At this stage, the name of the xib file is simply remembered, from which, in which case, you need to load the view .

There is one exception related to the heirs of the UITableViewController , which you need to know about (thanks to muryk habraiser ):

@interface HabrTestProjectTableViewController : UITableViewController { } @end /*      HabrTestProjectTableViewController.xib      */ HabrTestProjectTableViewController * tableViewController = [[HabrTestProjectTableViewController alloc] init]; /*     UITableViewController,     xib  */ HabrTestProjectTableViewController * tableViewController = [[HabrTestProjectTableViewController alloc] initWitNib:@"HabrTestProjectTableViewController" bundle:nil]; 


Important: At this stage, there is neither the view itself nor the Outlets.

 @interface HabrTestProjectViewController : UIViewController { UIButton * _likeButton; NSDictionary * _userDictionary; } @property(nonatomic, retain) IBOutlet UIButton * likeButton; - (IBAction)buttonClicked:(id)sender; @end HabrTestProjectViewController * controller = [[HabrTestProjectViewController alloc] init]; /*       */ NSLog(@"%@", controller.likeButton); /* (null) */ /*    */ if (controller.view) { NSLog(@"View was created"); /*   , .. controller.view  view */ } /*    */ if ([controller isViewLoaded]) { NSLog(@"View was created"); /*    ,  view  */ } 


The created UIViewController can be in the “no view ” state for a long time, until it is deleted from memory. The creation of the view will only occur after the [ viewController view ] ( viewController.view ) method is called. You can do this, or UINavigationController , UITabBarController, and many others can do it for you.

As an example, when the UIViewController is in the "no view " state, there may be an option using UITabBarController ' , when initially it contains references to N controllers, and only the one that is currently displayed on the screen will have a view loaded. All the rest will wait until the user switches the taboo, or until the crooked inexperienced programmer calls nonVisibleViewcontroller.view .

Error # 1 (l): Access to Outlets Before Loading. View
Error # 2 (p): Loading the view before it is actually needed, ignoring the isViewLoaded method
Error # 3 (p): Creating visual components in the initWithNibName method

Creating a UIView


Once access to the view has occurred, there are three possible scenarios

And only after that, the viewDidLoad method will finally be called.

Before proceeding to the next item, you need to say a few words about creating a UIView that are inside of xibs. Imagine a situation that in xib'e is created by UIPrettyView , which, when initialized, should set itself the background color in pink. So, in order not to drag anyone over any part of the body, I will say right away - if the UIView is loaded from xib, the initWithCoder: initialization method will be called, otherwise (when created in code), the initWithFrame method will usually be called :
 /*      xib' */ - (id)initWithFrame:(CGRect)frame { ... [self setBackgroundColor:[UIColor pinkColor]]; // [self performInitializations]; ... } /*     xib' */ - (id)initWithCoder:(NSCoder *)coder { ... [self setBackgroundColor:[UIColor pinkColor]]; // [self performInitializations]; ... } /*        */ - (void)performInitializations { [self setBackgroundColor:[UIColor pinkColor]]; } 

After the view has loaded from xib, a good place to “finish” design elements is the viewDidLoad method. This is where you should create visual components that, for some reason, did not fall into the xib or loadView method.

Error # 4 (l): Confusion with initWithFrame: and initWithCoder:.

Handling view state changes


As already mentioned, viewDidLoad is the best place to continue controller initialization. Beginners, and not only programmers, have problems with this method. Very often, due to ignorance / misunderstanding of the life cycle of the controller, you can see the following code:
 - (void)viewDidLoad { [super viewDidLoad]; _userDictionary = [[NSDictionary alloc] init]; /*    */ } - (void)viewDidUnload { [super viewDidUnload]; } - (void)dealloc{ [_userDictionary release], _userDictionary = nil; [super dealloc]; } 


And now, it's time to look at UIViewController 's conditional life diagram .


As you can see (hopefully) from the diagram, the viewDidLoad method can be called more than once during the life of the controller. As a result, the code above may cause memory leaks with each new call to viewDidLoad

Error # 5 (l) : viewDidLoad is called once during the life of the controller.

In fact, it is very useful to use this method to restore the state of the view controller. So, very often in the examples you can find the position setting at UIScrollView , setting the visual components to the current state (active / inactive).

It should be noted that at this stage of the controller's life cycle, the dimensions of the view are not relevant, i.e. not as they will be after the display. Therefore, it is not recommended to use calculations based on the width / height of the view in the viewDidload method.

Error # 6 (u) : viewDidload to return the interface to its original state.
Error # 7 (l) : Sizing of components, calculations, use of width / height view

viewWillAppear and viewDidAppear

Methods that are called before and after the view appears on the screen.
In the case of animation (the appearance of the controller in a modal window, or the transition to UINavigationController 'e), viewWillAppear will be called before the animation, and viewDidAppear - after.
When calling viewWillAppear , the view is already in the view hierarchy and has actual dimensions, so that you can make calculations based on the width / height of the view .

viewWillDisappear and viewDidDisappear

Methods that are very rarely used by programmers. They work the same way as viewWillAppear and viewDidAppear , only the other way around;)

viewDidUnload

The greatest number of errors is most often found in this method.
Therefore, we must understand what is happening in it.

The method is called when the view has been unloaded from memory.
When calling this method, the outletlets are still in memory, but in fact, they are not relevant, because are not in the display hierarchy, but the next time viewDidLoad will be overwritten with new ones.

So, for the correct operation of this method and the program as a whole, it is necessary:
- Zero all outlets.
- If possible, save the view state to restore it in the next call to viewDidLoad .
- Do not call methods that will lead to loading view .
- Do not use outlets to store the state of the controller.

The task is simple and clear, but 90% of novice developers do not know about it and forget.

 - (void)viewDidUnload { /*    */ [super viewDidUnload]; /*  ,   .        view */ UIView * observer = self.view; [[NSNotificationCenter defaultCenter] removeObserver:view]; } 

Error # 8 (p) The viewDidUnload method is empty and looks like [super viewDidUnload]
Error # 9 (p) The viewDidUnload method does not release the outlets
Error # 10 (u) The viewDidUnload method does not save controller state
Error # 11 (l) Using Outlets to Store Controller Status

Processing memory warning


The didReceiveMemoryWarning method is called by the system when there is a shortage of memory.
By default, the implementation of this method for a controller that is not in the visible area will call, will release the view (after that _view == nil), which in turn will cause the viewDidUnload to be called .
In this method, you need to free up as much memory as possible, if you cannot free it, then reset it to the cache (to a file, for example)

Error # 12 (p) In-memory storage of resources that can be freely flushed to the cache in a file.

Destruction


From the dealloc features of the controller, it is necessary to note that it is not a fact that before this viewDidUnload will be called.
In implementations of some third-party libraries, for example, in Three20, you can find the call viewDidUnload directly in dealloc .
For the rest - follow the principles set forth in the Memory Management Programming Guide .

This is almost all.
Next will be given some explanations about common mistakes.

Explanation of frequent errors


Errors are conditionally divided into three types:
p - perfromance error (an error leading to memory leaks, reducing the speed of the program)
l - logic error (an error that can lead to program malfunctioning due to incorrect assumptions / beliefs)
u - user unfriendly error (Not exactly a mistake, rather just a reminder that the user interface should be user friendly)



Perhaps, for the first time enough. I hope the post was informative, and will help iOS developers to do really good, not only outside, but also inside the application.

Thank you all for your attention.

UPDATE . Corrected the viewDidUnload method description
UPDATE . Considered an exception for the heirs of UITableViewController in the method initWithNibName: bundle: (Thanks to the muryk habraiser )
UPDATE . At the request of workers, I spread a small project to secure the material

Source: https://habr.com/ru/post/129557/


All Articles