iOS应用程序启动时所有方法的调用顺序

一个应用程序的启动过程要包括代理的创建,控制器的加载和控制器view的加载,这其中有很多关于生命周期的方法,每个方法都是有先后顺序的,如果调用顺序拿不准,或者某段代码写的方法不恰当,就会遇到各种奇葩问题。

首先看一下应用程序的启动过程:

  1. 先加载Main函数.

  2. 在Main函数里的 UIApplicationMain方法中,创建Application对象创建Application的Delegate对象.

  3. 创建主循环,代理对象开始监听事件.

  4. 启动完毕会调用 didFinishLaunching方法,并在这个方法中创建UIWindow.

  5. 设置UIWindow的根控制器是谁.

  6. 如果有storyboard,会根据info.plist中找到应用程序的入口storyboard并加载箭头所指的控制器.

  7. 显示窗口.

本文考虑的是步骤3之后到步骤7结束时将要调用的方法
其中有AppDelegate, ViewController, MainView(控制器的View), ChildView(子控件的View)的18个方法

AppDelegate中的:

1. application:didFinishLaunchingWithOptions:
2. applicationDidBecomeActive:

ViewController中的:

3. loadView
4. viewDidLoad
5. load
6. initialize
7. viewWillAppear
8. viewWillLayoutSubviews
9. viewDidLayoutSubviews
10. viewDidAppear

MainView(控制器的View)中的:

11. initWithCoder(如果没有storyboard就会调用initWithFrame,这里两种方法视为一种)
12. awakeFromNib
13. layoutSubviews
14. drawRect

ChildView(子控件View)中的:

15. initWithCoder(如果没有storyboard就会调用initWithFrame,这里两种方法视为一种)
16. awakeFromNib
17. layoutSubviews
18. drawRect

执行顺序如下:

1. + (void)load;

这是应用程序启动就会调用的方法,在这个方法里写的代码最先调用.

2. + (void)initialize;

这个是需要用到本类时才调用,这个方法里一般写设置导航控制器的主题啊之类的,如果在后面的方法设置导航栏主题就晚了!(当然在上面的方法里也能写).

3. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;

这个方法里面会创建UIWindow,设置根控制器并展现,比如某些应用程序要加载授权页面也是在这加,也可以设置观察者,监听到通知切换根控制器.

4. ChildView - (instancetype)initWithCoder:(NSCoder *)aDecoder;

这里是万万没想到,childView的initwithcoder会在MainView的方法之前调用.

5. MainView - (instancetype)initWithCoder:(NSCoder *)aDecoder;

父类view的initwithCoder.

6. MainView - (void)awakeFromNib;

在这个方法里设置view的背景等一系列普通操作,不要写关于frame的还不准,在使用IB的时候才会涉及到此方法的使用,当.nib文件被加载的时候,会发送一个awakeFromNib的消息到.nib文件中的每个对象,每个对象都可以定义自己的awakeFromNib函数来响应这个消息,执行一些必要的操作.

7. ChildView - (void)awakeFromNib

子控件也有本方法,重写父类的方法。基本用法同上.

8. - (void)loadView;

创建视图的层次结构,这里需要注意,在没有创建控制器的view的情况下不能直接写 self.view 因为self.view的底层是:

1
2
3
if(_view == nil){
  _view = [self loadView];
}

所以这么写会直接造成死循环。
如果重写这个loadView方法里面什么都不写,会显示黑屏。
如果写了[super view]还要看前面的控制器在创建时是写的initWithNibName(指定了xib名字),还是写的普通的init。如果是后者还是黑屏。如果不在这个方法中,init的底层是会调用initWithNibName的,如果名字是MainViewController,会先在项目中找MainView.xib 找不到会再找MainViewController.xib。

9. - (void)viewDidLoad;

这里只是把视图元件加载完成,还没有开始布局不要设置关于 frame 之类的属性!有时可能会出现差20个像素点等状况。

10. - (void)viewWillAppear:(BOOL)animated;

视图将要出现,这个方法用的非常多,比如如果要设置导航栏的setNavigationBarHiden:animate: 就必须要在这里写,才能完美契合,不卡跳。 还有很多比如监听屏幕旋转啦,
viewWillTransitionToSize:可能要在本方法里再调一次,或者就是新到这个界面要reloadData或是自动下拉刷新等 都是写在本方法里。

11. - (void)viewWillLayoutSubviews;

视图将要布局子视图,苹果建议的设置界面布局属性的方法,这个方法和viewWillAppear里,系统的底层都是没有写任何代码的,也就是说这里面不写super也是可以的.

12. MainView - (void)layoutSubviews;

在这个方法里一般设置子控件的frame,因为这里相当于是布局基本完成了,设置时取到的frame或者是self.bounds才最准,如果在awakeFromeNib里写会不准确 。还有这里要切记千万不能把super layoutSubviews忘了,可能最后都很难找到这个BUG.

13. - (void)viewDidLayoutSubviews;

这个方法是没想到的,控制器的view的子控件还没有布局好呢,怎么这个控制器就已经说布局全部完成了?

14. ChildView - (void)layoutSubviews;

控制器的子控件里的子控件的布局就在这里写.

15. MainView - (void)drawRect:(CGRect)rect;

因为默认所有额UI控件都是画上去的,在这一步就是把所有的东西画上去,有时候需要用到Quartz2D的知识的时候都是在这个方法里话,但也是要注意别忘了写super,不然系统原本的东西就都画不上来了,这里要建议尽可能使用贝塞尔路径画图形,因为系统默认的那个上下文画法有时可能会内存泄露。drawRect方法只能在加载时调用一次,如果后面还需要调用,比如下载进度的圆弧,需要一直刷帧,就要使用setNeedsDisplay来定时多次调用本方法.

16. ChildView - (void)drawRect:(CGRect)rect;

view的子控件内部的画图方法,有时可以自己自定义label 中间带个删除线的(用来写打折前的原价) 就是在这里画根线.

17. - (void)viewDidAppear:(BOOL)animated;

把上面的画图都画完了,这里就会显示,视图完全加载完成。在这里的操作可能就是设置页面的一些动画,或者是设置tableView,collectionView,QQ聊天页面啥的滚动到底部scrollToIndexPath之类的代码操作.

18. - (void)applicationDidBecomeActive:(UIApplication *)application;

最后这是AppDelegate的应用程序获取焦点方法,真正到了这里,才是所有东西全部加载完毕,应用程序整装待发保持最佳状态等待用户操作。这个方法中一般会写关于弹出键盘的方法,比如有的用户登录界面为了更好的用户体验,就让你在刚打开程序来到登录界面的时候,光标的焦点就自动在账号的文本框里闪烁,也就是设置账号文本框为第一响应者。键盘在页面加载完毕后从下方弹出,这种代码一般就在本方法写.


END

Author: lei-wen
FinishTime: 2019.03.05

Share Comments