一個應用程序的啓動過程要包括代理的創建,控制器的加載和控制器view的加載,這其中有很多關於生命週期的方法,每個方法都是有先後順序的,如果調用順序拿不準,或者某段代碼寫的方法不恰當,就會遇到各種奇葩問題。本文不怕麻煩的在幾乎所有啓動時要調用的方法裏都用了 __FUNCTION__ 打印。結果還有有些地方出人意料的
首先回顧一下應用程序的啓動過程
①.先加載Main函數
②.在Main函數裏的 UIApplicationMain方法中,創建Application對象 創建Application的Delegate對象
③.創建主循環,代理對象開始監聽事件
④.啓動完畢會調用 didFinishLaunching方法,並在這個方法中創建UIWindow
⑤.設置UIWindow的根控制器是誰
⑥.如果有storyboard,會根據info.plist中找到應用程序的入口storyboard並加載箭頭所指的控制器
⑦.顯示窗口
本文考慮的時步驟③之後到步驟⑦結束時將要調用的方法
其中有AppDelegate,ViewController,MainView(控制器的View),ChildView(子控件的View)的18個方法
AppDelegate中的:
1.application:didFinishLaunchingWithOptions:
2.applicationDidBecomeActive:
ViewController中的:
3.loadView
4.viewDidLoad
5.load
6.initialize
7.viewWillAppear
8.viewWillLayoutSubviews
9.viewDidLayoutSubviews
10.viewDidAppear
MainView(控制器的View)中的:
11.initWithCoder(如果沒有storyboard就會調用initWithFrame,這裏兩種方法視爲一種)
12.awakeFromNib
13.layoutSubviews
14.drawRect
ChildView(子控件View)中的:
15.initWithCoder(如果沒有storyboard就會調用initWithFrame,這裏兩種方法視爲一種)
16.awakeFromNib
17.layoutSubviews
18.drawRect
那麼問題來了,不往下看你可以把上面的十八個方法排個順序麼?
下面的圖是Xcode6.3的beta2版
有時有變化也就是最後兩個方法有點出入
我更傾向於Xcode 6.1 覺得更科學 下面就是對各個方法的整理
1
|
+
( void )load; |
1.這是應用程序啓動就會調用的方法,在這個方法裏寫的代碼最先調用(董鉑然原創)
1
|
+
( void )initialize; |
2.這個是需要用到本類時才調用,這個方法裏一般寫 設置導航控制器的主題啊 之類的,如果在後面的方法設置導航欄主題就晚了!(當然在上面的方法裏也能寫)
1
|
-
( BOOL )application:(UIApplication
*)application didFinishLaunchingWithOptions:( NSDictionary *)launchOptions; |
3.這個方法裏面會創建UIWindow,設置根控制器並展現,比如某些應用程序要加載授權頁面也是在這加,也可以設置觀察者,監聽到通知切換根控制器
1
|
ChildView
- (instancetype)initWithCoder:( NSCoder *)aDecoder; |
4.這裏反正我是萬萬沒想到,childView的initwithcoder會在MainView的方法之前調用,父的都還沒出來,就先整子控件? 有了解比較透徹的博友懇請告訴我謝謝。
1
|
MainView
- (instancetype)initWithCoder:( NSCoder *)aDecoder; |
5.就是關於應用程序的數據存儲後的解檔操作。
1
|
MainView
- ( void )awakeFromNib; |
6.在這個方法裏設置view的背景等一系列普通操作,不要寫關於frame的還不準,在使用IB的時候纔會涉及到此方法的使用,當.nib文件被加載的時候,會發送一個awakeFromNib的消息到.nib文件中的每個對象,每個對象都可以定義自己的awakeFromNib函數來響應這個消息,執行一些必要的操作。
1
|
ChildView
- ( void )awakeFromNib |
7.子控件也有本方法,重寫父類的方法。基本用法同上
1
|
-
( void )loadView; |
8.創建視圖的層次結構,這裏需要注意,在沒有創建控制器的view的情況下不能直接寫 self.view 因爲self.view的底層是:
if(_view == nil){
_view = [self loadView]
}
所以這麼寫會直接造成死循環。
如果重寫這個loadView方法裏面什麼都不寫,會顯示黑屏。
如果寫了[super view]還要看前面的控制器在創建時是寫的initWithNibName(指定了xib名字),還是寫的普通的init。 如果是後者還是黑屏。
如果不在這個方法中,init的底層是會調用initWithNibName的,如果名字是MainViewController,會先在項目中找MainView.xib 找不到會再找MainViewController.xib。
1
|
-
( void )viewDidLoad; |
9.臥槽,這個方法是當年用的最多的方法,但是在之後的開發中就會發現越來越不靠譜,很多東西都還沒加載完畢,各種取值都不準確,很少在這裏面寫東西了。 這裏只是把視圖元件加載完成,還沒有開始佈局不要設置關於 frame 之類的屬性!有時可能會出現差20個像素點等狀況。
1
|
-
( void )viewWillAppear:( BOOL )animated; |
10.視圖將要出現,這個方法用的非常多,比如如果要設置導航欄的setNavigationBarHiden:animate: 就必須要在這裏寫,才能完美契合,不卡跳。 還有很多比如監聽屏幕旋轉啦,
viewWillTransitionToSize:可能要在本方法裏再調一次,或者就是新到這個界面要reloadData或是自動下拉刷新等 都是寫在本方法裏。
1
|
-
( void )viewWillLayoutSubviews; |
11.視圖將要佈局子視圖,蘋果建議的設置界面佈局屬性的方法,這個方法和viewWillAppear裏,系統的底層都是沒有寫任何代碼的,也就是說這裏面不寫super 也是可以的
1
|
MainView
- ( void )layoutSubviews; |
12.在這個方法裏一般設置子控件的frame,因爲這裏相當於是佈局基本完成了,設置時取到的frame或者是self.bounds才最準,如果在awakeFromeNib裏寫會不準確 。還有這裏要切記千萬不能把super layoutSubviews忘了,可能最後都很難找到這個bug
1
|
-
( void )viewDidLayoutSubviews; |
13.這個方法我也是玩玩沒想到,控制器的view的子控件還沒有佈局好呢,怎麼這個控制器就已經說佈局全部完成了?那後邊的佈局就不等了? 有獨到見解的也懇請你告訴我,這其中蘋果的意思到底是什麼。
1
|
ChildView
- ( void )layoutSubviews; |
14.控制器的子控件裏的子控件的佈局就在這裏寫了。
1
|
MainView
- ( void )drawRect:(CGRect)rect; |
15. 因爲默認所有額UI控件都是畫上去的,在這一步就是把所有的東西畫上去,有時候需要用到Quartz2D的知識的時候都是在這個方法裏話,但也是要注意別忘了寫super,不然系統原本的東西就都畫不上來了,這裏要建議儘可能使用貝塞爾路徑畫圖形,因爲系統默認的那個上下文畫法有時可能會內存泄露。drawRect方法只能在加載時調用一次,如果後面還需要調用,比如下載進度的圓弧,需要一直刷幀,就要使用setNeedsDisplay來定時多次調用本方法
1
|
ChildView
- ( void )drawRect:(CGRect)rect; |
16.view的子控件內部的畫圖方法,有時可以自己自定義label 中間帶個刪除線的(用來寫打折前的原價) 就是在這裏畫根線 。
1
|
-
( void )viewDidAppear:( BOOL )animated; |
17.把上面的畫圖都畫完了,這裏就會顯示,視圖完全加載完成。在這裏的操作可能就是設置頁面的一些動畫,或者是設置tableView,collectionView,QQ聊天頁面啥的滾動到底部scrollToIndexPath之類的代碼操作。
1
|
-
( void )applicationDidBecomeActive:(UIApplication
*)application; |
18.最後這是AppDelegate的應用程序獲取焦點方法,真正到了這裏,纔是所有東西全部加載完畢,應用程序整裝待發保持最佳狀態等待用戶操作。這個方法中一般會寫關於彈出鍵盤的方法,比如有的用戶登錄界面爲了更好的用戶體驗,就讓你在剛打開程序來到登錄界面的時候,光標的焦點就自動在賬號的文本框裏閃爍,也就是設置賬號文本框爲第一響應者。鍵盤在頁面加載完畢後從下方彈出,這種代碼一般就在本方法寫。