本文遵循“署名-非商業用途-保持一致”創作公用協議
轉載請保留此句:太陽火神的美麗人生 - 本部落格專註於 敏捷開發及移動和物聯裝置研究:iOS、Android、Html5、Arduino、pcDuino,否則,出自本部落格的文章拒絕轉載或再轉載,謝謝合作。
24K 標題黨!
不過內容絕對夠細節而全面,僅針對啟動流程這一小塊塊喲!
iOS 應用啟動流程,這個話題早在09年就非常熟悉,然而時隔多年,不知是否還熟悉,尤其 StoryBoard 的引入,那麼下面就一起來看看吧,如果確實說明白了,給個評論,或哪裡有不足,需要完善,也給個指點。
由於 Objective-C 是對 C 的擴充,那麼 main 函數理所當然地繼承了程式入口的位置,而不像安卓,雖然它的程式進入點可能也是 main ,但那是掩埋在系統架構之內根源處的,也或許叫別的名字,想瞭解可參考Android系統啟動過程。
在 XCode 5.1.1 (2014-07-20 周日,此時 iOS 8 已經發布,但還未正式上架應用,beta 3 據說已經可以供開發人員償鮮)中建立一個單視圖應用 (Single View Application)。
XCode 工程中總有很多羅裡巴山的檔案,不過這也正是它先進之處,控制權集中,撒出多個點,來供開發人員配置以改變應用的運行效果,或許用傻瓜式的應用架構方式更貼切一些,不過,像 iOS 這樣不開源的架構,是否長此以往,我們的後代人是否會真的變成傻瓜,對架構內部的程式藝術完全不瞭解,喪失了這種架構能力了呢?!
切入主題,程式入口 main.m 檔案如下:
1 2 3 4 5 6 7 8 9 10 |
#import <uikit uikit.h=""> #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } </uikit> |
main 函數,和 C 語言中的一模一樣,帶兩個參數,argc 是參數個數,argv 是參數的字串數組,或者叫列表也行。
上面兩行中 #import 是Objective-C 中新引入的和 #include 巨集指令一樣的功能,引入其它標頭檔。
之所以要引入這個新的指令來包含標頭檔,是因為 #include 會存在重複引入的問題,即一個標頭檔被引入多次,那麼就可能定義了多個對象或變數,那是會出錯的。
所以,在 C 中,會使用巨集指令來判斷一個標頭檔中的預定義宏名是否存在,不存在則在宏條件判斷中使用 #include 引入標頭檔,否則不走這一宏分支,標頭檔就不會被引入。
Availability.h 標頭檔的精簡結構如下,雖然在 -Prefix.pch 檔案中是使用 #import 引入的該標頭檔,但也不可掉以輕心,因為該檔案還有可能在 C 代碼中使用 #include 引入,所以仍然加了 C 樣式的唯一引入宏結構:
1 2 3 4 5 6 7 |
#ifndef __AVAILABILITY__ #define __AVAILABILITY__ #include #endif /* __AVAILABILITY__ */ </availabilityinternal.h> |
這麼麻煩地對標頭檔進行處理,真是浪費時間,那麼 #import 應運而生,不用擔心標頭檔重複引入的問題。不過,別高興得太早,循環參考的問題,還是沒辦法解決的,這個就要用到另一個 @class 來聲明類名的存在性以便在聲明檔案中定義對象引用,而實際標頭檔引入放在實現檔案中。對於複雜些的邏輯,還是得靠你的整體把握能力來避勉循環參考:即 A 引入 B,B 引入 A 。
iOS 應用程式中最常,也算是基本約定俗成的,要引入兩個架構:UIKit 和 Foundation。
而在 main.m 檔案中,注釋掉 UIKit 的引入,程式也是會正常啟動並執行,因為並未用到 UIKit,只是程式模板預設加上的,可能是其它類型應用會用到吧,也許!
自動釋放池,這裡不作深入介紹,因為我們當前建立的應用,都預設是支援 ARC (自動引用計數 Automatic Reference Counting)的,所以使用這種配套的自動釋放池方法。
@autoreleasepool {
早先的方式,已經過時,這種新的方式,也支援 MRC(手動引用計數 Manual Reference Counting)源檔案的引入,只不過需要對源檔案編譯部分進行相應參數設定,需加上
相反,在早期的 MRC 項目中,所使用的方式已經過時,當下的 XCode 不會給你建立這樣的模板代碼,而且在用以 ARC 為主的工程時,那個也不會得到先行編譯的很好處理。如果確實需要的話,在 MRC 項目中引入 ARC 的源檔案,在該 ARC 源檔案的編譯選項中,輸入
ARC 無非就是先行編譯時,由編譯器根據需要替你把需要加上的 MRC 中的相應記憶體管理的代碼加上去,而非真正的動態記憶體管理,即記憶體回收,所以它的效率和記憶體佔用率要優質得多。
接下來,
將成為重點內容,它就相當於 MFC 中 WinMain 函數。
對於有表單的應用,它要呈現介面元素,並響應使用者的操作,比如滑鼠、鍵盤、觸屏等使用者操作事件,以及行動裝置的各類感應器事件等等。
這就要求帶表單的應用,要能夠迴圈查詢到這樣的事件,或許事件直接觸發可能會更快捷和節省資源,但那樣的後果是耦合性太強了。
所以,輪詢和事件觸發相結合,適當的分配,才能達到預期的效果。事件由系統分發給每個或者行動裝置的當前應用,這需要由事件緩衝池來完成,而每個應用輪詢期自身得到的事件,或由外部置入事件池,或由內部發生,得到這個事件後,再進行觸發,而將需要直接調用的回呼函數進行緩衝,注意啦,在應用內,變成了緩衝事件回呼函數,而非事件本身,事件本身相當於直接傳遞。由此就很好地解決了輪詢帶來的低效率和高資源佔用與直接回調的耦合緊密等等弱點。
UIApplicationMain 函數有四個參數:
1 |
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); |
第一個和第二個參數是 C 程式體的傳入參數,直接傳入,不做任何加工;
第三個參數是要建立的 iOS 應用執行個體的類,該類是 UIApplication 或其子類,每個應用僅此一個;
第四個參數是要建立的應用代理類,該類的功能其實是可以在應用類中完成的,不過這種代理模式能很好地把這部分功能從應用類中分離出來,這也是蘋果採用代理模式處理各種事件響應的高明之處。開發人員只要按代理方法的要求進行處理就可以了,對於統一規範預期工作任務很有協助,不至於由於開發人員的疏忽,而把程式結構搞得混亂。
下面就針對 UIApplicationMain 未指定代理類和指定代理類兩種情況對進一步的應用啟動過程作以簡要敘述。
還有就是主表單的來源,應用類執行個體會從 xxx-Info.plist 中讀取相關配置資訊。
其中 Main nib file base name 決定了從 nib 檔還是 StoryBoard 中擷取應用的視窗資訊,
1、如果是 nib,那麼主視窗也從該檔案中讀取,接下來,就可以從應用代理中,給主視窗設定根視圖控制器;
如果 UIApplicationMain 的第四個參數應用代理類為 nil 的話,應用不知如何找到應用代理類進行執行個體化,它就會在這個主 nib 檔中尋找,尋找的規則是:
(這句話是錯的,不是第四參為空白,才從 nib 中取應用代理,而是指定了主 nib ,就會預設在這裡找,如果找不到,再使用第四參)
a、主 nib 檔中的 File's Owner 是當前應用類,所以設成 UIApplication 或你指定的子類,然後在主 nib 中添加一個對象,設定它為應用代理類,
並從 File's Owner 拉線到剛加入的設成應用代理類的對象上,這時會彈出選項,選擇 Outlets 下的 delegate,這樣就把應用代理類關聯到了應用類的代理引用上了。
以下 UIApplication.h 中 UIApplication 的聲明可以讓你明白應用類與應用代理類的關係是組合的方式,至於物件導向複用機制的兩種方式繼承與組合,可以參見 繼承和組合:
1 2 3 4 5 |
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIApplication : UIResponder <uiactionsheetdelegate> { @package id <uiapplicationdelegate> _delegate; </uiapplicationdelegate></uiactionsheetdelegate> |
b、如果仍找不到的話,也即 File's Owner 所對應的應用類的 delegate 未指定應用代理類,就會償試使用 UIApplicationMain 的第四個參數應用代理類。
c、如果 主 nib 中 File's Owner 沒給應用的 delegate 指定代理類,而且 UIApplicationMain 的第四個參數也為 nil,這時應用一樣會啟動, nib 中的主視窗一樣會顯示,但沒有代理類,無法接收到應用類的應用生命週期的相關事件。
2、如果是 StoryBoard,主視窗就由應用自動建立,並將 StoryBoard 的第一個視圖控制器作為主視窗的根視圖控制器。
3、如果為空白,則應用就不管主視窗的事情了,由開發人員自已來搞定,此時運行,會在模擬器中看到黑屏應用,即沒有視窗,當然了,狀態列作為視窗的一部分。
那麼,主視窗從哪兒建立呢?這就涉及到應用代理類了,因為視窗是可以動態建立並指定的,而且視窗是可以有多個的,但主視窗只有一個,並且只有一個處於可見狀態。
寫了一下午,邊測試,邊確認,邊構思、組織語言與代碼,尚缺一些實際測試過程的截圖確認,這個後續再補上。
好像是缺點什麼,當時想到了,專註於某一個話題後再回到上面來俯視,發現忘記了......
回頭再說,想起來,隨時補充。
當然了,這其中還包括,查到的與我這裡話題相當的十幾二十篇非常不錯的參考資料,先建個所有Chrome 的標籤組吧,今天真的有點累了,該天再續。