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)