一個(gè)程序從main函數(shù)開(kāi)始啟動(dòng)。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
可以看到main函數(shù)會(huì)調(diào)用UIApplicationMain函數(shù),它的四個(gè)參數(shù)的意思是:
argc: 代表程序在進(jìn)入main函數(shù)時(shí)的參數(shù)的個(gè)數(shù)。默認(rèn)為1。
argv: 代表包含的各個(gè)參數(shù)。默認(rèn)為程序的名字。
principalClassName: UIApplication或者它的子類(lèi)的名字, 如果傳入的是nil, 則表示UIApplication的名字, 即@"UIApplication"。
delegateClassName: UIApplication的代理的名字。
在UIApplicationMain函數(shù)中,根據(jù)傳入的UIApplication名稱(chēng)和它的代理的名稱(chēng),會(huì)主要做下面的事情:
- 根據(jù)傳入的名稱(chēng)創(chuàng)建UIApplication對(duì)象。
- 根據(jù)傳入的代理名稱(chēng)創(chuàng)建UIApplication代理對(duì)象。
- 開(kāi)啟事件循環(huán)(如果不進(jìn)行循環(huán),那么在main函數(shù)結(jié)束后程序就結(jié)束了。要保證程序創(chuàng)建后可以一直存在)。
- 解析Info.plist文件。
- 會(huì)在Info.plist文件里查找
Main storyboard file base name這個(gè)Key對(duì)應(yīng)的Value是否有值。如果有值,則表示之后會(huì)通過(guò)Storyboard加載控制器,AppDelegate會(huì)接收到didFinishLaunchingWithOptions消息(程序啟動(dòng)完成的時(shí)候),此時(shí)Storyboard會(huì)進(jìn)行一系列的加載操作(后面會(huì)具體說(shuō));如果沒(méi)有值,則不會(huì)通過(guò)Storyboard加載控制器,接著AppDelegate會(huì)接收到didFinishLaunchingWithOptions消息(程序啟動(dòng)完成的時(shí)候),在這個(gè)時(shí)候需要我們通過(guò)代碼的方式加載控制器。 - 注意Info.plist中
Main storyboard file base name這個(gè)Key并不是真正的Key,而是蘋(píng)果為了增強(qiáng)可讀性才這樣寫(xiě)的,真正的Key為UIMainStoryboardFile(可以通過(guò)Info.plist文件的源代碼查看)。 - 這就是在想要用代碼方式創(chuàng)建控制器而不是Storyboard創(chuàng)建控制器的時(shí)候?yàn)槭裁聪纫獙?code>Main Interface設(shè)置為空白,這樣在解析Info.plist文件的時(shí)候才會(huì)知道不通過(guò)Storyboard創(chuàng)建控制器。
- 由此可以知道,解析Info.plist文件這一操作主要是看我們用的是Storyboard方式加載還是代碼的方式加載。默認(rèn)
Main storyboard file base name為Main,也就是通過(guò)Storyboard方式加載控制器。
- 會(huì)在Info.plist文件里查找
現(xiàn)在具體分析一下,通過(guò)Storyboard方式加載控制器和代碼方式加載控制器。
通過(guò)Storyboard
通過(guò)Storyboard,主要做了下面的事情(這些事情不需要我們做,是系統(tǒng)自動(dòng)完成的,在程序啟動(dòng)完成的時(shí)候):
-
創(chuàng)建窗口。
創(chuàng)建一個(gè)UIWindow的實(shí)例用來(lái)顯示界面。
-
設(shè)置窗口的根控制器。
- 根據(jù)Storyboard的設(shè)置,創(chuàng)建一個(gè)控制器。
- 并且設(shè)置這個(gè)控制器為之前創(chuàng)建的window的根控制器。
-
顯示窗口。(相當(dāng)于后面提到的makeKeyAndVisible)
設(shè)置self.window可見(jiàn)并且設(shè)置UIApplication的keyWindow。
在這一步中將根控制器的view添加到window上。
通過(guò)代碼方式
通過(guò)代碼的方式,需要我們?cè)?code>didFinishLaunchingWithOptions方法中進(jìn)行加載控制器的相關(guān)操作。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *viewController = [[UIViewController alloc] init];
self.window.rootViewController = viewController;
// 此時(shí)根控制器的view還沒(méi)有加到self.window上
[self.window makeKeyAndVisible];
// 此時(shí)根控制器的view加到self.window上
return YES;
}
其實(shí)這里所做和系統(tǒng)所做是一樣的。(相當(dāng)于系統(tǒng)的做法)
首先創(chuàng)建窗口,得到一個(gè)正確的UIWindow實(shí)例對(duì)象用來(lái)顯示界面。(self.window是系統(tǒng)自帶的屬性)
-
接著設(shè)置窗口的根控制器。
- 不再根據(jù)Storyboard中的設(shè)置加載,此時(shí)需要我們自己創(chuàng)建控制器。
- 設(shè)置這個(gè)控制器為self.window的根控制器。
- 注意這個(gè)時(shí)候根控制器的view還沒(méi)有加到self.window上,當(dāng)窗口要顯示的時(shí)候,才會(huì)把窗口的根控制器的view添加到窗口。(可以輸出self.window.subViews來(lái)驗(yàn)證)
-
顯示窗口。
[self.window makeKeyAndVisible]實(shí)際上做了下面的事:首先,將self.window設(shè)置為UIApplication的
keyWindow,這么做是方便我們以后查看UIApplication的主窗口是哪一個(gè)。接著,讓self.window可見(jiàn),相當(dāng)于執(zhí)行的代碼是:
self.window.hidden = NO;這么做的原因是self.window默認(rèn)hidden = YES,所以需要讓其顯示出來(lái)。
那么既然
makeKeyAndVisible執(zhí)行的是以上的操作,實(shí)際上將[self.window makeKeyAndVisible]替換為self.window.hidden = NO,那么界面也會(huì)正常顯示出來(lái),因?yàn)?code>makeKeyAndVisible內(nèi)部就是這么做的。但是此時(shí)并沒(méi)有設(shè)置UIApplication的keyWindow,為了以后方便訪問(wèn),還是用makeKeyAndVisible更好一點(diǎn)。經(jīng)過(guò)這一步,界面將要顯示,此時(shí)根控制器的view會(huì)加到self.window上以正常顯示。
-
這里有一點(diǎn)要注意:
系統(tǒng)創(chuàng)建的AppDelegate自帶一個(gè)屬性位于.h文件中:
@property (strong, nonatomic) UIWindow *window;當(dāng)用Storyboard的方式加載控制器,在應(yīng)用啟動(dòng)完成的時(shí)候(didFinishLaunchingWithOptions)需要一個(gè)UIWindow的實(shí)例來(lái)顯示界面,所以Apple提供了這個(gè)window屬性。系統(tǒng)根據(jù)storyboard自動(dòng)創(chuàng)建一個(gè)window,然后將window賦值給這個(gè)window屬性,以保證完成之后的工作。
當(dāng)用代碼的方式加載控制器,同樣的,首先也需要一個(gè)UIWindow的實(shí)例來(lái)顯示界面,因?yàn)椴皇褂肧toryboard所以這次要我們自己創(chuàng)建window。此時(shí)有兩種做法,第一種是在didFinishLaunchingWithOptions方法中創(chuàng)建一個(gè)UIWindow對(duì)象:
UIWindow *myWindow = [[UIWindow alloc] initWithFrame:...];但是如果用這種方法運(yùn)行程序會(huì)發(fā)現(xiàn)界面依然無(wú)法顯示出來(lái),因?yàn)榇藭r(shí)
myWindow是一個(gè)局部變量,當(dāng)didFinishLaunchingWithOptions方法執(zhí)行完畢這個(gè)變量就會(huì)銷(xiāo)毀。所以更好的辦法是直接使用系統(tǒng)提供的window屬性:self.window = [[UIWindow alloc] initWithFrame:...];之前的例子也是這么做的。
另外,仔細(xì)觀察會(huì)發(fā)現(xiàn)這個(gè)window屬性的修飾符是
strong,而不是weak。想想之前使用weak來(lái)修飾一個(gè)控件是因?yàn)檫@個(gè)控件會(huì)被加到一個(gè)view中,這個(gè)view的subViews數(shù)組會(huì)有強(qiáng)引用指向控件,所以用weak是沒(méi)有問(wèn)題的。現(xiàn)在這種情況,因?yàn)閣indow控件不會(huì)被加到其他view中,即沒(méi)有其他的強(qiáng)指針指向這個(gè)對(duì)象,所以在創(chuàng)建的時(shí)候需要將修飾符設(shè)置成strong以保證創(chuàng)建出的window不會(huì)被銷(xiāo)毀。(Apple創(chuàng)建的window屬性的修飾符是strong)
UIWindow的補(bǔ)充
window是有層級(jí)的,并且可以有多個(gè)window同時(shí)存在。比如:狀態(tài)欄就是一個(gè)window,鍵盤(pán)也是一個(gè)window。
可以通過(guò)設(shè)置UIWindow的對(duì)象的windowLevel屬性來(lái)調(diào)整層級(jí)。
self.window.windowLevel = UIWindowLevelStatusBar;
window共有三種等級(jí):UIWindowLevelNormal,UIWindowLevelStatusBar UIWindowLevelAlert。如果三種等級(jí)同時(shí)出現(xiàn)在屏幕上,那么alert在最上面,statusBar在中間,normal則在最下面。
注意:如果一個(gè)程序中有多個(gè)window,控制器默認(rèn)會(huì)把狀態(tài)欄隱藏。
解決辦法:關(guān)閉控制器對(duì)狀態(tài)欄的控制,(為Info.plist增加View controller-based status bar appearance這個(gè)key并設(shè)置為NO)這樣這些window以及狀態(tài)欄就可以按層級(jí)關(guān)系正常顯示。