
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 :
CreatureCreate view- (BOOL) isViewLoaded
- loadView
- viewDidLoad
- (UIView *) initWithFrame: (CGRect) frame
- (UIView *) initWithCoder: (NSCoder *) coder
Handling view state changes- viewDidLoad
- viewWillAppear: (BOOL) animated
- viewDidAppear: (BOOL) animated
- viewWillDisappear: (BOOL) animated
- viewDidDisappear: (BOOL) animated
- viewDidUnload
Processing memory warningDestructionSome 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 * tableViewController = [[HabrTestProjectTableViewController alloc] init]; 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); if (controller.view) { NSLog(@"View was created"); } if ([controller isViewLoaded]) { NSLog(@"View was created"); }
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.
ViewError # 2 (p): Loading the
view before it is actually needed, ignoring the isViewLoaded method
Error # 3 (p): Creating visual components in the
initWithNibName methodCreating a UIView
Once access to the
view has occurred, there are three
possible scenarios- calling the overridden loadView method
- calling a non-overridden loadView method that will either load the view from the xib file, or create an empty UIView
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
: - (id)initWithFrame:(CGRect)frame { ... [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
viewDidLoadError # 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
viewviewWillAppear 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]; 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