生命流程小結

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閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容