筆記-iOS應用程式的啟動過程

佐籩發表於2019-02-27

程式的啟動

  1. 使用Xcode開啟一個專案,很容易會發現一個檔案main.m檔案,此處就是應用的入口。
  2. 程式啟動時,先執行main函式,main函式是iOS程式的入口點
  3. 內部會呼叫UIApplicationMain函式
  4. UIApplicationMain裡會建立一個UIApplication物件
  5. 然後建立UIAPPlication的delegate物件
  6. 然後AppDelegate,開啟一個訊息迴圈(main runloop)每當監聽到對應的系統事件時,就會通知AppDelegate
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
複製程式碼

UIApplication物件是應用程式的象徵,每一個應用都有自己的UIApplication物件,而且是單例的。通過[UIApplication sharedApplication]可以獲得這個單例物件,一個iOS程式啟動後建立的第一個物件就是UIApplication物件,利用UIApplication物件,能進行一些應用級別的操作。

UIApplicationMain函式實現如下

int UIApplicationMain {
    int argc,
    char *argv[],
    NSString *principalClassName,
    NSString *delegateClassName
}
複製程式碼

第一個參數列示程式在進入mian函式是的引數的個數,預設為1
第二個參數列示裝載函式的陣列(包含的各個引數),預設為程式的名字
第三個引數是UIApplication類名或其子類名,若是nil,則預設使用UIApplication類名。
第四個引數是協議UIApplicationDelegate的例項化物件名,這個物件就是UIApplication物件監聽到系統變化的時候通知其執行的相應方法。

在UIApplicationMain函式中,根據傳入的UIApplication名稱和它的代理的名稱,會主要做下面的事情:

  • 根據傳入的名稱建立UIApplication物件
  • 根據傳入的代理名稱建立UIApplication代理物件
  • 開啟事件迴圈(如果不進行迴圈,那麼在main函式結束後程式就結束了。要保證程式建立後可以一直存在)
  • 解析Info.plist檔案:
    會在Info.plist檔案裡查詢Main storyboard file base name這個Key對應的Value是否有值。如果有值,則表示之後回通過Storyboard載入控制器,APPDelegate會接收到didFinishLaunchingWithOptions訊息(程式啟動完成的時候),此時storyboard會進行一系列的載入操作;如果沒有值,則不會通過storyboard載入控制器,接著AppDelegate會接收到didFinishLaunchingWithOptions訊息(程式啟動完成的時候),這個時候需要我們通過程式碼的方式載入控制器。

啟動完畢會呼叫didFinishLaunching方法,並在這個方法中建立UIWindow,設定AppDelegate的window屬性,並設定UIWindow的根控制器

- (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;
}
複製程式碼

首先建立視窗,得到一個正確的UIWindow例項物件用來顯示介面(self.window是系統自帶的屬性)。接著設定視窗的根控制器。自己建立控制器,設定這個控制器為self.window的根控制器。注意這個時候根控制器的view還沒有加到self.window上,當視窗要顯示的時候,才會把視窗的根控制器的view新增到視窗。
顯示視窗:

[self.window makeKeyAndVisible] // 實際做了下面的事
複製程式碼

首先將self.window設定為UIApplication的keyWindow,這麼做是方便我們以後檢視UIApplication的主視窗是哪一個。
接著,讓self.window可見,相當於執行的程式碼是:

self.window.hidden = NO;
複製程式碼

那麼既然makeKeyAndVisible執行的是以上操作,實際上將[self.window makeKeyAndVisible]替換為self.window.hidden = NO,那麼介面也會正常顯示出來,因為makeKeyAndVisible內部就是這麼做的。但是此時並沒有設定UIApplication的keyWindow,為了以後方便訪問,還是用makeKeyAndVisible更好一點。

UIWindow的補充

window是有層級的,並且可以有多個window同時存在。比如:狀態列就是一個window,鍵盤也是一個window。
可以通過設定UIWindow的物件的windowLevel屬性來調整層級。
self.window.windowLevel = UIWindowLevelStatusBar;
window共有三種等級:UIWindowLevelNormal, UIWindowLevelStatusBar, UIWindowLevelAlert。如果三種等級同事出現在螢幕上,那麼alert在最上面,statusBar在中間,normal則在最下面。
注意:如果一個程式中有多個window,控制器預設會把狀態列隱藏。
解決辦法:關閉控制器對狀態列的控制,(為Info.plist增加View controller-based status bar appearance這個key並設定為NO),這樣這些window以及狀態列就可以按層級關係正常顯示。

概覽

  1. 先執行main函式,main內部會呼叫UIApplicationMain函式
  2. UIApplicationMain函式裡面做了什麼事情
    • 建立UIApplication物件
    • 建立UIApplication的delegate物件——AppDelegate
    • 開啟一個訊息迴圈:每監聽到對應的系統事件時,就會通知AppDelegate
    • 為應用程式建立一個UIWindow物件(繼承自UIView),設定為AppDelegate的window屬性
    • 載入Info.plist檔案,讀取最主要storyboard檔案的名稱
    • 載入最主要的storyboard檔案,建立白色箭頭所指的控制器物件
    • 並且設定上一步建立的控制器為UIWindow的rootViewController屬性(根控制器)
    • 展示UIWindow,展示之前會將新增rootViewController的view到UIWindow上面(在這一步才回建立控制器的view)
[window addSubview:window.rootViewController.view];
複製程式碼

相關文章