
这个默认的 iOS 程序就是从 main 函数开始执行的,但是在 main 函数中我们其实只能看到一个方法,这个方法内部是一个消息循环(相当于一个死循环),因此运行到这个方法 UIApplicationMain 之后程序不会自动退出,而只有当用户手动关闭程序这个循环才结束。我们看下这个方法定义:
这个方法有四个参数:
关于返回值,即便声明了返回值,但该函数也从不会返回。
也就是说当执行 UIApplicationMain 方法后这个方法会根据第三个参数 principalClassName 创建对应的 UIApplication 对象,这个对象会根据第四个参数 delegateClassName 创建 AppDelegate 并指定此对象为 UIApplication 的代理;同时 UIApplication 会开启一个消息循环不断监听应用程序的各个活动,当应用程序生命周期发生改变 UIApplication 就会调用代理对应的方法。
既然应用程序 UIApplication 是通过代理和外部交互的,那么我们就有必要清楚 AppDelegate 的 *** 作细节,在这个类中定义了生命周期的各个事件的执行方法:
简要说下我们不同的 *** 作,程序运行结果:
通过简单的 *** 作,大家对整个运行周期有了个大概的了解。再附上一张图,让大家有个清晰的认识:
总览 UIViewController 生命周期:
下面创建了一个 TestViewController 类,了解下整个过程:
TestViewController.m:
在 ViewController.m 中:
我们在创建 TestViewController 实例时,可以通过以下两种方法:
我们经常使用的是第二种创建方法,其实第二种方法默认实现了第一种的方法,只不过两个参数默认传的是 nil。
当 TestVeiwController 通过 xib 加载的时候,看下 viewDidLoad 之前发生了什么:
无 xib:
TestVeiwController 通过 storyboard 加载:
控制台输出:
我们可以看到通过 storyboard 实例化与 init 实例化在 loadView 方法调用之前走的是不同的方法。我们看下这几个方法的不同:
此方法发生在 nib 加载之前。
调用此方法进行 Controller 初始化,与 nib 加载无关。nib 的加载是懒加载,当 Controller 需要加载其视图时,才会加载此方法中指定的 nib。
可以看出该方法初始化的 Controller 不是从 nib 创建的。
此方法发生在 nib 加载期间。
所有 archived 对象的初始化使用此方法。nib 中存储的对象就是 archived 对象,所以此方法是 nib 加载对象时使用的初始化方法。
当从 nib 创建 UIViewController 时使用此方法。
此方法发生在 nib 中所有对象都已完全加载完之后。
如果 initWithCoder 是 unarchiving 开始,那此方法就是结束。
在此方法中创建视图。
我们可以通过下图来理解它的逻辑:
每次访问 view 时,就会调用 self.view 的 get 方法,在 get 方法中判断 self.view==nil ,不为 nil 就直接返回 view,等于 nil 就去调用 loadView 方法。loadView 方法会去判断有无指定 storyBord/Xib 文件,如果有就去加载 storyBord/Xib 描述的控制器 view,如果没有则系统默认创建一个空的 view,赋给 self.view。loadView 方法有可能被多次调用(每当访问 self.view 并且为 nil 时就会调用一次);
系统会自动为我们加载 view,我们完全没必要手动创建 view。
视图将要被展示的时候调用。
其调用的时机与视图所在层次有关。例如我们常用的 push 与 present *** 作改变了当前视图层次,都会触发此方法。
1、那么 UIAlertController 也是 present *** 作怎么没有触发呢?
因为 UIAlertController 在另一个 window 上,view 在自己所在的 window 中层次并没有改变,所以不会触发,同理在锁屏以及进入后台时也不会触发。
2、如果控制器 B 被展示在另一个控制器 A 的 popover 中,那么被展示的控制器 B 在消失后,控制器 A 并不会调用此方法。
官方原文:
例如我们使用的 addSubview 方法,如下:
AViewController.m 中:
当我们将 BViewController 从 AViewController 中移除后,并不会触发 AViewController 的 viewWillAppear 方法。
视图渲染完成后调用,与 viewWillAppear 配套使用。
这两个方法发生在 viewWillAppear 与 viewDidAppear 之间。
viewWillDisappear 与 viewDidDisappear 配套使用。
两个方法的调用时机同 viewWillAppear 和 viewDidAppear 道理相同。
这两个方法是收到内存警告时调用的。
在 iOS5 以及之前使用的方法,iOS6 及之后已经废弃。在收到内存警告时,在此方法中将 view 置为 nil
收到内存警告时,系统自动调用此方法,回收占用大量内存的视图数据。我们一般不需要在这里做额外的 *** 作。如果要自己处理一些额外内存,重写时需要调用父类方法,即 [super didReceiveMemoryWarning] 。
UIViewController 释放时调用此方法。UIViewController 的生命周期到此结束。
当我们重写此方法时,ARC 环境下不需要调用父类方法,MRC 环境下需要调用父类方法,即 [super dealloc] 。
本篇主要介绍了 APP 的生命周期,以及 UIViewController 的生命周期,对我们程序开发的过程有了更清晰的认识。
参考资料:
https://www.cnblogs.com/kenshincui/p/3890880.html
https://www.quora.com/Cocoa-API-What-is-the-difference-between-initWithCoder-initWithNibName-and-awakeFromNib-1
1,单个viewController的生命周期
①,initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib)
②,loadView:加载view
③,viewDidLoad:view加载完毕
④,viewWillAppear:控制器的view将要显示
⑤,viewWillLayoutSubviews:控制器的view将要布局子控件
⑥,viewDidLayoutSubviews:控制器的view布局子控件完成
这期间系统可能会多次调用viewWillLayoutSubviews 、 viewDidLayoutSubviews 俩个方法
⑦,viewDidAppear:控制器的view完全显示
⑧,viewWillDisappear:控制器的view即将消失的时候
这期间系统也会调用viewWillLayoutSubviews 、viewDidLayoutSubviews 两个方法
⑨,viewDidDisappear:控制器的view完全消失的时候
⑩,didReceiveMemoryWarning(内存满时)
当程序发出一个内存警告--->
系统询问控制器有View吗--->如果有View
系统询问这个View能够销毁吗---->通过判断View是否在Windown上面,如果不在,就表示可以销毁
如果可以销毁,就执行viewWillUnLoad()----->对你的View进行一次release,此时View就为nil
然后调用viewDidUnLoad()----->一般还会在这个方法里将一些不需要属性清空
2,多个viewControllers跳转
当我们点击push的时候首先会加载下一个界面然后才会调用界面的消失方法
initWithCoder:(NSCoder *)aDecoder:ViewController2(如果用xib创建的情况下)
loadView:ViewController2
viewDidLoad:ViewController2
viewWillDisappear:ViewController1将要消失
viewWillAppear:ViewController2将要出现
viewWillLayoutSubviewsViewController2
viewDidLayoutSubviewsViewController2
viewWillLayoutSubviews:ViewController1
viewDidLayoutSubviews:ViewController1
viewDidDisappear:ViewController1完全消失
viewDidAppear:ViewController2完全出现
3,相关解释
①,loadView()
若控制器有关联的 Nib 文件,该方法会从 Nib 文件中加载 view;如果没有,则创建空白 UIView 对象。
自定义实现不应该再调用父类的该方法。
②,viewDidLoad()
view 被加载到内存后调用viewDidLoad()。
重写该方法需要首先调用父类该方法。
该方法中可以额外初始化控件,例如添加子控件,添加约束。
该方法被调用意味着控制器有可能(并非一定)在未来会显示。
在控制器生命周期中,该方法只会被调用一次。
③,viewWillAppear()
该方法在控制器 view 即将添加到视图层次时以及展示 view 时所有动画配置前被调用。
重写该方法需要首先调用父类该方法。
该方法中可以进行 *** 作即将显示的 view,例如改变状态栏的取向,类型。
该方法被调用意味着控制器将一定会显示。
在控制器生命周期中,该方法可能会被多次调用。
注意:******
ViewControllerA present一个ViewControllerB,如果是iOS13 默认UIModalPresentationAutomatic样式,当ViewControllerB dismiss 到 ViewControllerA的时候,不调用ViewControllerA里面的,viewWillAppear方法和viewDidAppear方法。
modalPresentationStyle设置成UIModalPresentationFullScreen的模式,会调用viewWillAppear方法和viewDidAppear方法。
④,viewWillLayoutSubviews()
该方法在通知控制器将要布局 view 的子控件时调用。
每当视图的 bounds 改变,view 将调整其子控件位置。
该方法可重写以在 view 布局子控件前做出改变。
该方法的默认实现为空。
该方法调用时,AutoLayout 未起作用。
在控制器生命周期中,该方法可能会被多次调用。
⑤,viewDidLayoutSubviews()
该方法在通知控制器已经布局 view 的子控件时调用。
该方法可重写以在 view 布局子控件后做出改变。
该方法的默认实现为空。
该方法调用时,AutoLayout 已经完成。
在控制器生命周期中,该方法可能会被多次调用。
⑥,viewDidAppear()
该方法在控制器 view 已经添加到视图层次时被调用。
重写该方法需要首先调用父类该方法。
该方法可重写以进行有关正在展示的视图 *** 作。
在控制器生命周期中,该方法可能会被多次调用。
⑦,viewWillDisappear()
该方法在控制器 view 将要从视图层次移除时被调用。
类似 viewWillAppear()。
该方法可重写以提交变更,取消视图第一响应者状态。
⑧,viewDidDisappear()
该方法在控制器 view 已经从视图层次移除时被调用。
类似 viewDidAppear()
该方法可重写以清除或隐藏控件。
⑨,didReceiveMemoryWarning()
当内存预警时,该方法被调用。
不能直接手动调用该方法。
该方法可重写以释放资源、内存。
⑩,deinit
控制器销毁时(离开堆),调用该方法。
可以移除通知,调试循环测试
总结:
当屏幕旋转,view 的 bounds 改变,其内部的子控件也需要按照约束调整为新的位置,因此也调用了 viewWillLayoutSubviews() 和 viewDidLayoutSubviews()。
ViewControllerA present一个ViewControllerB,如果是iOS13 默认UIModalPresentationAutomatic样式,当ViewControllerB dismiss 到 ViewControllerA的时候,不调用ViewControllerA里面的,viewWillAppear方法和viewDidAppear方法。
modalPresentationStyle设置成UIModalPresentationFullScreen的模式,会调用viewWillAppear方法和viewDidAppear方法。
若 loadView() 没有加载 view,viewDidLoad() 会一直调用 loadView() 加载 view,因此构成了死循环,程序即卡死。 原文
上面说的有些混乱了,下边看一下转场A viewwilldisappear,AviewDidDisappear和 B viewwillappear和viewdidappear的各种情况的出现顺序:
Push: A-willDisappear-->B-willAppear-->A-didDisappear-->B-didAppear
fullScreenPresent: A-willDisappear-->B-willAppear-->B-didAppear-->A-didDisappear
OtherPresent: B-willAppear-->B-didAppear(因为A没有消失)
TabBar: B-willAppear-->A-willDisappear-->A-didDisappear-->B-didAppear
在每一种转场下,appear 与 disappear 都有一些不一样的顺序,一定要分清楚,不能一概而论。
以下第一个出现的都是will,第二个都是did,will永远在did之前,因为一个是将要,一个是已经
push:abab(最正常)
fullScreenPresent:abba
otherPresent:bb(a不消失,所以不调用)
tabbar:baab
附加面试题:load和initialize的区别
******:load是只要类所在的文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会调用load方法,即使类文件被引用进来,如果没有使用,那么initialize不会被调用。
调用方式
1、load是根据函数地址直接调用
2、initialize是通过objc_msgSend调用
调用时刻
1、load是runtime加载类、分类的时候调用(只会调用一次)
2、initialize是类第一次接收到消息的时候调用, 每一个类只会initialize一次(如果子类没有实现initialize方法, 会调用父类的initialize方法, 所以父类的initialize方法可能会调用多次)
load和initializee的调用顺序
1、load:
先调用类的load, 在调用分类的load
先编译的类, 优先调用load, 调用子类的load之前, 会先调用父类的load
先编译的分类, 优先调用load
2、initialize
先初始化分类, 后初始化子类
通过消息机制调用, 当子类没有initialize方法时, 会调用父类的initialize方法, 所以父类的initialize方法会调用多次
链接:https://www.jianshu.com/p/bd2197d5e547
在- (void)viewWillAppear:(BOOL)animated 与 - (void)viewDidAppear:(BOOL)animated 之间还要执行两个关键的方法:
** 给各个控制器view子控件添加约束**
程序第一次运行,显示第一个控制器的时候,按照如下顺序执行:
由第一个控制器右上角item按钮跳到第二个控制器的时候,执行打印:
由第二个控制器View中的button按钮跳到第三个控制器的时候,执行打印:
第二个控制器返回至第一个控制器的执行打印:
第三个控制器返回至第二个控制器的执行打印:
源码链接
UIViewController的生命周期及iOS程序执行顺序
:学豆-控制器的生命周期
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)