一個程序從main函數(shù)開始啟動楼咳。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
可以看到main函數(shù)會調用UIApplicationMain
函數(shù)但指,它的四個參數(shù)的意思是:
argc: 代表程序在進入main函數(shù)時的參數(shù)的個數(shù)。默認為1。
argv: 代表包含的各個參數(shù)。默認為程序的名字式廷。
principalClassName: UIApplication或者它的子類的名字, 如果傳入的是nil, 則表示UIApplication的名字, 即@"UIApplication"。
delegateClassName: UIApplication的代理的名字芭挽。
在UIApplicationMain函數(shù)中滑废,根據(jù)傳入的UIApplication名稱和它的代理的名稱,會主要做下面的事情:
- 根據(jù)傳入的名稱創(chuàng)建UIApplication對象袜爪。
- 根據(jù)傳入的代理名稱創(chuàng)建UIApplication代理對象策严。
- 開啟事件循環(huán)(如果不進行循環(huán),那么在main函數(shù)結束后程序就結束了饿敲。要保證程序創(chuàng)建后可以一直存在)。
- 解析Info.plist文件逛绵。
- 會在Info.plist文件里查找
Main storyboard file base name
這個Key對應的Value是否有值怀各。如果有值,則表示之后會通過Storyboard加載控制器术浪,AppDelegate會接收到didFinishLaunchingWithOptions消息(程序啟動完成的時候)瓢对,此時Storyboard會進行一系列的加載操作(后面會具體說);如果沒有值胰苏,則不會通過Storyboard加載控制器硕蛹,接著AppDelegate會接收到didFinishLaunchingWithOptions消息(程序啟動完成的時候),在這個時候需要我們通過代碼的方式加載控制器硕并。 - 注意Info.plist中
Main storyboard file base name
這個Key并不是真正的Key法焰,而是蘋果為了增強可讀性才這樣寫的,真正的Key為UIMainStoryboardFile
(可以通過Info.plist文件的源代碼查看)倔毙。 - 這就是在想要用代碼方式創(chuàng)建控制器而不是Storyboard創(chuàng)建控制器的時候為什么先要將
Main Interface
設置為空白埃仪,這樣在解析Info.plist文件的時候才會知道不通過Storyboard創(chuàng)建控制器。 - 由此可以知道陕赃,解析Info.plist文件這一操作主要是看我們用的是Storyboard方式加載還是代碼的方式加載卵蛉。默認
Main storyboard file base name
為Main
颁股,也就是通過Storyboard方式加載控制器。
- 會在Info.plist文件里查找
現(xiàn)在具體分析一下傻丝,通過Storyboard方式加載控制器和代碼方式加載控制器甘有。
通過Storyboard
通過Storyboard,主要做了下面的事情(這些事情不需要我們做葡缰,是系統(tǒng)自動完成的亏掀,在程序啟動完成的時候):
-
創(chuàng)建窗口。
創(chuàng)建一個UIWindow的實例用來顯示界面运准。
-
設置窗口的根控制器幌氮。
- 根據(jù)Storyboard的設置,創(chuàng)建一個控制器胁澳。
- 并且設置這個控制器為之前創(chuàng)建的window的根控制器该互。
-
顯示窗口。(相當于后面提到的makeKeyAndVisible)
設置self.window可見并且設置UIApplication的keyWindow韭畸。
在這一步中將根控制器的view添加到window上宇智。
通過代碼方式
通過代碼的方式,需要我們在didFinishLaunchingWithOptions
方法中進行加載控制器的相關操作胰丁。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *viewController = [[UIViewController alloc] init];
self.window.rootViewController = viewController;
// 此時根控制器的view還沒有加到self.window上
[self.window makeKeyAndVisible];
// 此時根控制器的view加到self.window上
return YES;
}
其實這里所做和系統(tǒng)所做是一樣的随橘。(相當于系統(tǒng)的做法)
首先創(chuàng)建窗口,得到一個正確的UIWindow實例對象用來顯示界面锦庸。(self.window是系統(tǒng)自帶的屬性)
-
接著設置窗口的根控制器机蔗。
- 不再根據(jù)Storyboard中的設置加載,此時需要我們自己創(chuàng)建控制器甘萧。
- 設置這個控制器為self.window的根控制器萝嘁。
- 注意這個時候根控制器的view還沒有加到self.window上,當窗口要顯示的時候,才會把窗口的根控制器的view添加到窗口扬卷。(可以輸出self.window.subViews來驗證)
-
顯示窗口牙言。
[self.window makeKeyAndVisible]
實際上做了下面的事:首先,將self.window設置為UIApplication的
keyWindow
怪得,這么做是方便我們以后查看UIApplication的主窗口是哪一個咱枉。接著,讓self.window可見徒恋,相當于執(zhí)行的代碼是:
self.window.hidden = NO;
這么做的原因是self.window默認hidden = YES蚕断,所以需要讓其顯示出來。
那么既然
makeKeyAndVisible
執(zhí)行的是以上的操作入挣,實際上將[self.window makeKeyAndVisible]
替換為self.window.hidden = NO
基括,那么界面也會正常顯示出來,因為makeKeyAndVisible
內部就是這么做的财岔。但是此時并沒有設置UIApplication的keyWindow
风皿,為了以后方便訪問河爹,還是用makeKeyAndVisible
更好一點。經(jīng)過這一步桐款,界面將要顯示咸这,此時根控制器的view會加到self.window上以正常顯示。
-
這里有一點要注意:
系統(tǒng)創(chuàng)建的AppDelegate自帶一個屬性位于.h文件中:
@property (strong, nonatomic) UIWindow *window;
當用Storyboard的方式加載控制器魔眨,在應用啟動完成的時候(didFinishLaunchingWithOptions)需要一個UIWindow的實例來顯示界面媳维,所以Apple提供了這個window屬性。系統(tǒng)根據(jù)storyboard自動創(chuàng)建一個window遏暴,然后將window賦值給這個window屬性侄刽,以保證完成之后的工作。
當用代碼的方式加載控制器朋凉,同樣的州丹,首先也需要一個UIWindow的實例來顯示界面,因為不使用Storyboard所以這次要我們自己創(chuàng)建window杂彭。此時有兩種做法墓毒,第一種是在didFinishLaunchingWithOptions方法中創(chuàng)建一個UIWindow對象:
UIWindow *myWindow = [[UIWindow alloc] initWithFrame:...];
但是如果用這種方法運行程序會發(fā)現(xiàn)界面依然無法顯示出來,因為此時
myWindow
是一個局部變量
亲怠,當didFinishLaunchingWithOptions方法執(zhí)行完畢這個變量就會銷毀所计。所以更好的辦法是直接使用系統(tǒng)提供的window屬性:self.window = [[UIWindow alloc] initWithFrame:...];
之前的例子也是這么做的。
另外团秽,仔細觀察會發(fā)現(xiàn)這個window屬性的修飾符是
strong
主胧,而不是weak
。想想之前使用weak
來修飾一個控件是因為這個控件會被加到一個view中习勤,這個view的subViews數(shù)組會有強引用指向控件讥裤,所以用weak
是沒有問題的。現(xiàn)在這種情況姻报,因為window控件不會被加到其他view中,即沒有其他的強指針指向這個對象间螟,所以在創(chuàng)建的時候需要將修飾符設置成strong
以保證創(chuàng)建出的window不會被銷毀吴旋。(Apple創(chuàng)建的window屬性的修飾符是strong
)
UIWindow的補充
window是有層級的,并且可以有多個window同時存在厢破。比如:狀態(tài)欄就是一個window荣瑟,鍵盤也是一個window。
可以通過設置UIWindow的對象的windowLevel
屬性來調整層級摩泪。
self.window.windowLevel = UIWindowLevelStatusBar;
window共有三種等級:UIWindowLevelNormal
笆焰,UIWindowLevelStatusBar
UIWindowLevelAlert
。如果三種等級同時出現(xiàn)在屏幕上见坑,那么alert在最上面嚷掠,statusBar在中間捏检,normal則在最下面。
注意:如果一個程序中有多個window,控制器默認會把狀態(tài)欄隱藏不皆。
解決辦法:關閉控制器對狀態(tài)欄的控制贯城,(為Info.plist增加View controller-based status bar appearance
這個key并設置為NO)這樣這些window以及狀態(tài)欄就可以按層級關系正常顯示。