iOS之UIApplication,viewController生命流程小结
App
App生命的开始入口同所有的C语言程序一样是main()。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 首先会创建一个
autoreleasepool
。 - 然后调用
UIApplicationMain方法
。 -
UIApplicationMain
方法会创建一个UIApplication 单例
即[UIApplication sharedApplication]
. - 开启一个
**run loop**
,********我们的****App****不同一般的程序代码从头执行到尾,一行行执行完毕后,程序就结束了****,****而是等待我们的输入响应。全依赖于****runloop
****。 - 经过上面的流程,说明App已经开始执行,这样就会通知 通过
UIApplicationMain
第三个参数指定的AppDelegate
,执行delegate
-application:didFinishLaunchingWithOptions:
方法。 - ...
- 直到App结束。
##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 {
}
//启动程序
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];
按语义做了两件事
- 指定[UIApplication shareApplication] 的keywindow。
- 设置window为可见。
所以在
[self.window makeKeyAndVisible];
方法之前是获取不到keywindow的。
特别要注意这之前的代码有想当然认为keywindow
肯定存在的而使用了。
我曾经就在初始化首页时,再获取网络数据时,把错误提示信息,通过keywindow
上显示的情况,未做判断导致crash掉的经历。
status bar
本身就是一个window
,window
是有层级,优先级越大越靠前。
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没有值
优先加载MyViewController.xib文件来创建view
加载MyView.xib文件来创建view
如果没有找到上面所述的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来加载。
- initWithNibName 是ViewController的一个方法, xib对应的File's Owner 应该是ViewController类。
- 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。
- initWithNibName 初始化的view是延迟加载的,没用到view时,是不会加载的,而loadNibNamed则是立即加载。
UIView
UIView 是iOS 中界面元素的基础,所有的界面元素都是继承它。在iOS应用中看的到的,摸得到的都是它。
- 绘图和动画(CALayer 和 CAAnimation 实现)
- 事件处理 (继承了UIResponder)
initWithCoder: 与 awakeFromNib
之所把这两个放到UIView这块说明,主要是因为自定义的UIView通过xib加载的情况比较多。
- initWithCoder
这是一个NSCoding协议里面的一个方法,只要遵从了NSCoding协议的类都有-initWithCoder:这个方法。xib文件的类常常会用到这个方法,估计是因为xib文件编码解码用了-initWithCoder:和-encodeWithCoder:,
所以如果我们重写-initWithCoder:,就可以对xib文件的初始化作代码上的调整。
它的机制是用从unarchiver得到的数据初始化一个对象。
- 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)