生命流程小结

iOS之UIApplication,viewController生命流程小结

App

App生命的开始入口同所有的C语言程序一样是main()。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  1. 首先会创建一个autoreleasepool
  2. 然后调用UIApplicationMain方法
  3. UIApplicationMain方法会创建一个UIApplication 单例[UIApplication sharedApplication].
  4. 开启一个**run loop**,********我们的****App****不同一般的程序代码从头执行到尾,一行行执行完毕后,程序就结束了****,****而是等待我们的输入响应。全依赖于****runloop****。
  5. 经过上面的流程,说明App已经开始执行,这样就会通知 通过UIApplicationMain第三个参数指定的AppDelegate,执行delegate -application:didFinishLaunchingWithOptions:方法。
  6. ...
  7. 直到App结束。
main()

##UIApplication
UIApplication 主要负责把来自外部的各种事件传递给内部,包括硬件事件,和其他各种系统事件。硬件事件会被传递给window,而其他系统事件,如应用开启关闭,被压入后台,被重新调起,收到push通知等则是转发给application的delegate

//当程序已经开始执行了 UIApplication 会调用这个方法通知我们,我们可以在这个方法里自己创建Window,然后设置想要的根控制器。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

//程序将要进入后台时触发,代表着应用将失去焦点,不接受用户的事件
- (void)applicationWillResignActive:(UIApplication *)application

//程序已经完全进入了后台时触发
- (void)applicationDidEnterBackground:(UIApplication *)application

//当程序即将从后台调起进入前台是触发
- (void)applicationWillEnterForeground:(UIApplication *)application

//程序已经获得焦点,进入了前台。
- (void)applicationDidBecomeActive:(UIApplication *)application

//程序即将结束时通知
- (void)applicationWillTerminate:(UIApplication *)application {
}

in
//启动程序
2016-03-17 16:02:36.940 LifeCycle[2148:117099]  main()
2016-03-17 16:02:37.277 LifeCycle[2148:117099]  AppDelegate    >>>>    application:didFinishLaunchingWithOptions:
2016-03-17 16:02:37.278 LifeCycle[2148:117099]  AppDelegate    >>>>    UIApplicaton Class: MyApplication
2016-03-17 16:02:37.324 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationDidBecomeActive:
2016-03-17 16:02:37.329 LifeCycle[2148:117099]  ViewController    >>>>    viewDidLoad
2016-03-17 16:02:37.331 LifeCycle[2148:117099]  ViewController    >>>>    viewWillAppear:
2016-03-17 16:02:37.358 LifeCycle[2148:117099]  ViewController    >>>>    viewDidAppear:

//压入后台
2016-03-17 16:02:42.468 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationWillResignActive:
2016-03-17 16:02:43.015 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationDidEnterBackground:

//重新调起到前台
2016-03-17 16:02:49.693 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationWillEnterForeground:
2016-03-17 16:02:50.208 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationDidBecomeActive:

//双击home键准备kill App
2016-03-17 16:02:55.787 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationWillResignActive:
2016-03-17 16:02:57.351 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationDidEnterBackground:
2016-03-17 16:02:57.353 LifeCycle[2148:117099]  ViewController    >>>>    viewWillDisappear:
2016-03-17 16:02:57.354 LifeCycle[2148:117099]  ViewController    >>>>    viewDidDisappear:
2016-03-17 16:02:57.355 LifeCycle[2148:117099]  AppDelegate    >>>>    applicationWillTerminate:

下面是手动创建window指定window的代码其中需要注意的点是
[self.window makeKeyAndVisible]; 按语义做了两件事

  1. 指定[UIApplication shareApplication] 的keywindow。
  2. 设置window为可见。

所以在[self.window makeKeyAndVisible];方法之前是获取不到keywindow的。
特别要注意这之前的代码有想当然认为keywindow肯定存在的而使用了。

我曾经就在初始化首页时,再获取网络数据时,把错误提示信息,通过keywindow上显示的情况,未做判断导致crash掉的经历。

status bar 本身就是一个windowwindow是有层级,优先级越大越靠前。

UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelAlert;
UIKIT_EXTERN const UIWindowLevel UIWindowLevelStatusBar __TVOS_PROHIBITED;
打印出来对应的值为 0 2000 1000
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[UIViewController new]];
    
    [self.window makeKeyAndVisible];
    return YES;
}

##UIViewController

- (void)loadView; // This is where subclasses should create their custom view hierarchy if they aren't using a nib. Should never be called directly.

- (void)viewDidLoad; // Called after the view has been loaded. For view controllers created in code, this is after -loadView. For view controllers unarchived from a nib, this is after the view is set.

- (void)viewWillAppear:(BOOL)animated;    // Called when the view is about to made visible. Default does nothing
- (void)viewDidAppear:(BOOL)animated;     // Called when the view has been fully transitioned onto the screen. Default does nothing
- (void)viewWillDisappear:(BOOL)animated; // Called when the view is dismissed, covered or otherwise hidden. Default does nothing
- (void)viewDidDisappear:(BOOL)animated;  // Called after the view was dismissed, covered or otherwise hidden. Default does nothing

//下面两个方法是在willAppear 与didAppear中间执行,
//此时view的frame与bounds都是准确的了,所以布局的操作应该在下面两个方法中执行
// Called just before the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary. The default is a nop.
- (void)viewWillLayoutSubviews NS_AVAILABLE_IOS(5_0);
// Called just after the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary. The default is a nop.
- (void)viewDidLayoutSubviews NS_AVAILABLE_IOS(5_0);

- (void)didReceiveMemoryWarning; // Called when the parent application receives a memory warning. On iOS 6.0 it will no longer clear the view by default.

- (void)viewWillUnload NS_DEPRECATED_IOS(5_0,6_0) __TVOS_PROHIBITED;
- (void)viewDidUnload NS_DEPRECATED_IOS(3_0,6_0) __TVOS_PROHIBITED; // Called after the view controller's view is released and set to nil. For example, a memory warning which causes the view to be purged. Not invoked as a result of -dealloc.


ViewController 生命流程图

为了测试上面流程图的左上角和左下角的流程

对ViewController进行了收到内存警告时将view置nil的操作。

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSLog(@" %@    >>>>    %@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    NSLog(@" %@    >>>>    %@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
    self.view = nil;
}
@end

我们在navcontroller的栈为viewcontroller栈底,SecondViewController 在栈顶,也就是当前页的情况下,模拟发起了内存警告。日志打印如下:

//收到内存警告
2016-03-17 16:12:08.133 LifeCycle[2207:121782] Received memory warning.
2016-03-17 16:12:08.135 LifeCycle[2207:121782]  AppDelegate    >>>>    applicationDidReceiveMemoryWarning:
//ViewController收到内存警告,将view置nil了。
2016-03-17 16:12:08.135 LifeCycle[2207:121782]  ViewController    >>>>    didReceiveMemoryWarning 
2016-03-17 16:12:08.136 LifeCycle[2207:121782]  SecondViewController    >>>>    didReceiveMemoryWarning

//在SecondViewController 进行了返回到ViewController页面操作。
2016-03-17 16:12:10.325 LifeCycle[2207:121782]  SecondViewController    >>>>    viewWillDisappear:

//ViewController检查View发现为nil,调用loadView发现没有实现此方法,
//于是又通过xib加载了view,同时发出viewDidLoad通知。
2016-03-17 16:12:10.326 LifeCycle[2207:121782]  ViewController    >>>>    viewDidLoad
2016-03-17 16:12:10.327 LifeCycle[2207:121782]  ViewController    >>>>    viewWillAppear:
2016-03-17 16:12:10.834 LifeCycle[2207:121782]  SecondViewController    >>>>    viewDidDisappear:
2016-03-17 16:12:10.834 LifeCycle[2207:121782]  ViewController    >>>>    viewDidAppear:

1.ARC 以后

-(void)viewWillUnload NS_DEPRECATED_IOS(5_0,6_0) __TVOS_PROHIBITED;

-(void)viewDidUnload NS_DEPRECATED_IOS(3_0,6_0) __TVOS_PROHIBITED;

********已经弃用********,即不在前台Controller在收到内存警告后,********不会********再执行view的release操作,并且view置nil。

2.为了模拟左上角的情况,我们在收到内存警告后,对view进行了置nil操作。

通过打印 2016-03-17 16:12:10.326 LifeCycle[2207:121782] ViewController >>>> viewDidLoad

可以看出在返回ViewController时,会去再次检查view发现没有就通过xib加载,加载成功后调用了viewDidLoad。

********程序启动**** ViewController =PUSH=> SecondViewController =Back=> ViewController ****打印情况********

2016-03-17 15:46:20.698 LifeCycle[2044:108959]  main()
2016-03-17 15:46:20.766 LifeCycle[2044:108959]  AppDelegate    >>>>    application:didFinishLaunchingWithOptions:
2016-03-17 15:46:20.766 LifeCycle[2044:108959]  AppDelegate    >>>>    UIApplicaton Class: MyApplication
2016-03-17 15:46:20.787 LifeCycle[2044:108959]  AppDelegate    >>>>    applicationDidBecomeActive:
2016-03-17 15:46:20.789 LifeCycle[2044:108959]  ViewController    >>>>    viewDidLoad
2016-03-17 15:46:20.790 LifeCycle[2044:108959]  ViewController    >>>>    viewWillAppear:
2016-03-17 15:46:20.800 LifeCycle[2044:108959]  ViewController    >>>>    viewDidAppear:
2016-03-17 15:46:23.116 LifeCycle[2044:108959]  ViewController    >>>>    viewWillDisappear:
2016-03-17 15:46:23.116 LifeCycle[2044:108959]  SecondViewController    >>>>    loadView
2016-03-17 15:46:23.117 LifeCycle[2044:108959]  SecondViewController    >>>>    viewDidLoad
2016-03-17 15:46:23.117 LifeCycle[2044:108959]  SecondViewController    >>>>    viewWillAppear:
2016-03-17 15:46:23.626 LifeCycle[2044:108959]  ViewController    >>>>    viewDidDisappear:
2016-03-17 15:46:23.626 LifeCycle[2044:108959]  SecondViewController    >>>>    viewDidAppear:
2016-03-17 15:46:32.202 LifeCycle[2044:108959]  SecondViewController    >>>>    viewWillDisappear:
2016-03-17 15:46:32.203 LifeCycle[2044:108959]  ViewController    >>>>    viewWillAppear:
2016-03-17 15:46:32.709 LifeCycle[2044:108959]  SecondViewController    >>>>    viewDidDisappear:
2016-03-17 15:46:32.710 LifeCycle[2044:108959]  ViewController    >>>>    viewDidAppear:

如果一个ViewController,对应几个可以实例化view的xib,使用initWithNibName来实例化

[[MyViewController alloc]initWithNibName:@"myView2" bundle:nil];

注意:控制器的view是延迟加载的

用到view时,就会调用控制器的loadView方法加载view,********重写****loadView****方法,必须为****self.view****赋值,否则会生成一个默认的透明的****View****

如果没有通过loadVeiw加载view,loadView加载view的默认过程(UIViewController的默认实现)

1 > 如果nibName有值,就会加载对应的xib文件来创建view

2 > 如果nibName没有值

  1. 优先加载MyViewController.xib文件来创建view

  2. 加载MyView.xib文件来创建view

  3. 如果没有找到上面所述的xib文件,就会用代码创建一个透明的view

- (UIView *)view {
    if (_view == nil) {
        [self loadView];
        [self viewDidLoad];
    }
    return _view;
}

PUSH情况下,都是先调用 Disappear 再调用 Appear 方法,先调用will 再调用did。

如:
//ViewCotroller =push=> SecondViewController
ViewController    >>>>    viewWillDisappear:
SecondViewController    >>>>    viewWillAppear:
ViewController    >>>>    viewDidDisappear:
SecondViewController    >>>>    viewDidAppear:

//SecondViewController =back=> ViewController
SecondViewController    >>>>    viewWillDisappear:
ViewController    >>>>    viewWillAppear:
SecondViewController    >>>>    viewDidDisappear:
ViewController    >>>>    viewDidAppear:

但是presentViewController 出去时先后顺序是不一样的。

2016-03-17 16:34:24.269 LifeCycle[2346:132620]  ViewController    >>>>    viewDidAppear:

//viewDidLoad先调用了。
2016-03-17 16:34:27.944 LifeCycle[2346:132620]  SecondViewController    >>>>    viewDidLoad
2016-03-17 16:34:27.951 LifeCycle[2346:132620]  ViewController    >>>>    viewWillDisappear:
2016-03-17 16:34:27.951 LifeCycle[2346:132620]  SecondViewController    >>>>    viewWillAppear:
//viewDidAppear先调用了。
2016-03-17 16:34:28.456 LifeCycle[2346:132620]  SecondViewController    >>>>    viewDidAppear:
2016-03-17 16:34:28.456 LifeCycle[2346:132620]  ViewController    >>>>    viewDidDisappear:

2016-03-17 16:34:29.822 LifeCycle[2346:132620]  SecondViewController    >>>>    viewWillDisappear:
2016-03-17 16:34:29.823 LifeCycle[2346:132620]  ViewController    >>>>    viewWillAppear:
//viewDidAppear先调用了。
2016-03-17 16:34:30.328 LifeCycle[2346:132620]  ViewController    >>>>    viewDidAppear:2016-03-17 16:34:30.328 LifeCycle[2346:132620]  SecondViewController    >>>>    viewDidDisappear:

********所以打算通过这些方法的先后顺序来操作的同学需要注意了********

而上述显示流程能够被触发是依赖系统的这套机制的:

    [viewContrllor willMoveToParentViewController:self];
    [self addChildViewController: viewContrllor];
    [self.view addSubview: viewContrllor.view];
    viewContrllor.view.frame = self.view.bounds;
    [viewContrllor didMoveToParentViewController:self];
initWithNibName VS loadNibNamed

这两者没有多大联系但是容易混淆,因为都涉及到xib来加载。

  1. initWithNibName 是ViewController的一个方法, xib对应的File's Owner 应该是ViewController类。
  2. when using loadNibNamed:owner:options:, the File's Owner should be NSObject, the main view should be your class type, and all outlets should be hooked up to the view, not the File's Owner. loadNibName加载的xib,对应的File's Owner 必须是NSObject。
  3. initWithNibName 初始化的view是延迟加载的,没用到view时,是不会加载的,而loadNibNamed则是立即加载。

UIView

UIView 是iOS 中界面元素的基础,所有的界面元素都是继承它。在iOS应用中看的到的,摸得到的都是它。

  1. 绘图和动画(CALayer 和 CAAnimation 实现)
  2. 事件处理 (继承了UIResponder)
initWithCoder: 与 awakeFromNib

之所把这两个放到UIView这块说明,主要是因为自定义的UIView通过xib加载的情况比较多。

  1. initWithCoder

这是一个NSCoding协议里面的一个方法,只要遵从了NSCoding协议的类都有-initWithCoder:这个方法。xib文件的类常常会用到这个方法,估计是因为xib文件编码解码用了-initWithCoder:和-encodeWithCoder:,

所以如果我们重写-initWithCoder:,就可以对xib文件的初始化作代码上的调整。
它的机制是用从unarchiver得到的数据初始化一个对象。

  1. awakeFromNib
@interface NSBundle(UINibLoadingAdditions)
  • (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options;
    @end
    @interface NSObject(UINibLoadingAdditions)
  • (void)awakeFromNib;
  • (void)prepareForInterfaceBuilder NS_AVAILABLE_IOS(8_0);
    @end

>从上面代码可以看出,awakeFromNib 是 `NSObject` 的一个跟xib加载相关类别里面的一个方法。 
>
>当xib文件加载完的时候,会发送一个awakeFromNib的消息到xib文件中的每个对象。<br>
>由此可知,在创建对象时先调用initWithCoder,之后再调用awakeFromNib。
>>注意:<br>
>>
>>1. 从Nib加载是指此对象包含在Nib文件里面,当Nib被初始化时,其里面的对象会通过initWithCoder初始化,最后收到awakeFromNib方法回调。<br>
>>
>>2. UIViewController也实现了NSCoding协议,所以也都有这两个方法。<br>
>>
>>3. 如果是MyViewController 调用initWithNibName初始化,其MyViewController本身不会收到此回调。因为掉用initWithNibName来初始化,自身并不是通过Nib初始化的,在这个过程中通过xib初始化的是其对应的View以及xib里面的其他对象,故是不会调用到上述两个方法。
>>4. 如果此xib中,有个SecondViewController对象,那么SecondViewController会被通过xib初始化,这时SecondViewController 对象中的的此两个方法会被调用。

###响应者链
点击屏幕这样一个操作,App是如何响应的呢?

1. 硬件会把事件传给我们的App,由`UIApplication`对象分派事件。
2. `UIApplication` 将事件传给`Key Window`,由`Key Window`分派事件。
3. `Key Window` 开始查找 `View 层级`里面最上层的`ViewController` 的 `View`
4. 再找到这个`View`对于区域里最上层的`子View或Control(即Responder)`
5. 发现是`Button` 触发的`target/action`<br>
这个流程就是`查找可能的事件响应者`<br>
***再反过来***
6. Button 能响应这个事件吗,能就响应,不能则一直按上述反的流程一次询问到widnow,window也处理不了,就交给application。

>从application到window到view,每层中可以处理事件的对象都叫做responder,实现了NSResponder或者UIResponder 协议。Responder的定义,就是可以处理事件的对象。
>
>所以UIView与CALayer的最大区别也在于此,UIView实现了UIResponder协议,是Responder,可以响应或传递相关事件,而CALayer继承NSObject没有实现UIResponder协议,主要负责图层绘制和动画。

[iOS事件分发机制(一) hit-Testing](http://suenblog.duapp.com/blog/100031/iOS%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E6%9C%BA%E5%88%B6%EF%BC%88%E4%B8%80%EF%BC%89%20hit-Testing)<br>
[iOS事件分发机制(二)The Responder Chain](http://suenblog.duapp.com/blog/100032/iOS%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E6%9C%BA%E5%88%B6%EF%BC%88%E4%BA%8C%EF%BC%89The%20Responder%20Chain)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容