iOS啟動流程和生命周期

1. 啟動流程

1.1 準備知識

Mach-O
Executable 可執行文件
Dylib 動態庫
Bundle 無法被連接的動態庫,只能通過dlopen()加載
Image 指的是Executable,Dylib或者Bundle的一種
Framework 動態庫和對應的頭文件和資源文件的集合

Apple的操作系統的可執行文件格式幾乎都是mach-o,mach-o可以大致的分為三部分:

Header 頭部包含可以執行的CPU架構,比如x86,arm64
Load commands 加載命令,包含文件的組織架構和在虛擬內存中的布局方式
Data 數據,包含load commands中需要的各個段(segment)的數據,每一個Segment都得大小是Page的整數倍。

絕大多數mach-o包括以下三個段(支持用戶自定義Segment,但是很少使用)

__TEXT 代碼段 只讀,包括函數,和只讀的字符串,上圖中類似__TEXT,__text的都是代碼段
__DATA 數據段 讀寫,包括可讀寫的全局變量等,上圖類似中的__DATA,__data都是數據段
__LINKEDIT 包含了方法和變量的元數據(位置,偏移量),以及代碼簽名等信息。
dyld

dyld的全稱是dynamic loader,它的作用是加載一個進程所需要的image,它是開源的。

  • Virtual Memory

    虛擬內存是在物理內存上建立的一個邏輯地址空間,它向上(應用)提供了一個連續的邏輯地址空間,向下隱藏了物理內存的細節。
    虛擬內存使得邏輯地址可以沒有實際的物理地址,也可以讓多個邏輯地址對應到一個物理地址。

    虛擬內存被劃分為一個個大小相同的Page(64位系統上是16KB),提高管理和讀寫的效率。 Page又分為只讀和讀寫的Page。

    虛擬內存是建立在物理內存和進程之間的中間層。在iOS上,當內存不足的時候,會嘗試釋放那些只讀的Page,因為只讀的Page在下次被訪問的時候,可以再從磁盤讀取。如果沒有可用內存,會通知在后臺的App(也就是在這個時候收到了memory warning),如果在這之后仍然沒有可用內存,則會殺死在后臺的App。

  • Page fault
    在應用執行的時候,它被分配的邏輯地址空間都是可以訪問的,當應用訪問一個邏輯Page,而在對應的物理內存中并不存在的時候,這時候就發生了一次Page fault。當Page fault發生的時候,會中斷當前的程序,在物理內存中尋找一個可用的Page,然后從磁盤中讀取數據到物理內存,接著繼續執行當前程序。

  • Dirty Page & Clean Page
    如果一個Page可以從磁盤上重新生成,那么這個Page稱為Clean Page
    如果一個Page包含了進程相關信息,那么這個Page稱為Dirty Page
    像代碼段這種只讀的Page就是Clean Page。而像數據段(_DATA)這種讀寫的Page,當寫數據發生的時候,會觸發COW(Copy on write),也就是寫時復制,Page會被標記成Dirty,同時會被復制。

1.2 dyld2啟動流程

dyld2啟動流程
加載dyld到App進程
加載動態庫(包括所依賴的所有動態庫)
Rebase
Bind
初始化Objective-C Runtime
其它的初始化代碼
加載動態庫

dyld會首先讀取mach-o文件的Header和load commands。
接著就知道了這個可執行文件依賴的動態庫。例如加載動態庫A到內存,接著檢查A所依賴的動態庫,就這樣的遞歸加載,直到所有的動態庫加載完畢。通常一個App所依賴的動態庫在100-400個左右,其中大多數都是系統的動態庫,它們會被緩存到dyld shared cache,這樣讀取的效率會很高。

查看mach-o文件所依賴的動態庫,可以通過MachOView的圖形化界面(展開Load Command就能看到),也可以通過命令行otool。

Rebase && Bind

有兩種主要的技術來保證應用的安全:ASLR和Code Sign。

ASLR的全稱是Address space layout randomization,翻譯過來就是“地址空間布局隨機化”。App被啟動的時候,程序會被影射到邏輯的地址空間,這個邏輯的地址空間有一個起始地址,而ASLR技術使得這個起始地址是隨機的。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函數的地址。

Code Sign相信大多數開發者都知曉,這里要提一點的是,在進行Code sign的時候,加密哈希不是針對于整個文件,而是針對于每一個Page的。這就保證了在dyld進行加載的時候,可以對每一個page進行獨立的驗證。

mach-o中有很多符號,有指向當前mach-o的,也有指向其他dylib的,比如printf。那么,在運行時,代碼如何準確的找到printf的地址呢?

mach-o中采用了PIC技術,全稱是Position Independ code。當你的程序要調用printf的時候,會先在__DATA段中建立一個指針指向printf,在通過這個指針實現間接調用。dyld這時候需要做一些fix-up工作,即幫助應用程序找到這些符號的實際地址。主要包括兩部分

Rebase 修正內部(指向當前mach-o文件)的指針指向
Bind 修正外部指針指向

之所以需要Rebase,是因為剛剛提到的ASLR使得地址隨機化,導致起始地址不固定,另外由于Code Sign,導致不能直接修改Image。Rebase的時候只需要增加對應的偏移量即可。待Rebase的數據都存放在__LINKEDIT中。

Rebase解決了內部的符號引用問題,而外部的符號引用則是由Bind解決。在解決Bind的時候,是根據字符串匹配的方式查找符號表,所以這個過程相對于Rebase來說是略慢的。

Objective-C

Objective C是動態語言,所以在執行main函數之前,需要把類的信息注冊到一個全局的Table中。同時,Objective C支持Category,在初始化的時候,也會把Category中的方法注冊到對應的類中,同時會唯一Selector,這也是為什么當你的Cagegory實現了類中同名的方法后,類中的方法會被覆蓋。

另外,由于iOS開發是基于Cocoa Touch的,所以絕大多數的類起始都是系統類,所以大多數的Runtime初始化起始在Rebase和Bind中已經完成。

Initializers

接下來就是必要的初始化部分了,主要包括幾部分:

  • load(Swift已棄用,只能使用initialize)
  • C/C++靜態初始化對象和標記為__attribute__(constructor)的方法

1.3 dyld3啟動流程

上文的講解是dyld2的加載方式。而最新的是dyld3加載方式略有不同:

加載方式 process 注釋
dyld2 in-process 只有當應用程序被啟動的時候,dyld2才能開始執行任務。
dyld3 部分out-of-process和in-process。 out-of-process在App下載安裝和版本更新的時候會去執行。

out-of-process會做如下事情:

  • 分析Mach-o Headers
  • 分析依賴的動態庫
  • 查找需要Rebase & Bind之類的符號
  • 把上述結果寫入緩存

這樣,在應用啟動的時候,就可以直接從緩存中讀取數據,加快加載速度。

1.4 main之后

相對于開發者來說,main才是程序入口。下面是加載流程:

    1. main函數
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

UIApplicationMain函數有四個參數,最后一個參數是AppDelegate的類名,通常使用模板創建的AppDelegate是AppDelegate,如果我們想要改變它的名字,我們同樣需要在這里傳入對應的類名。

 UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
參數名 作用
argcargv ISO C標準的main函數的參數,直接傳遞給UIApplicationMain進行相關處理。參數包含應用程序何時從系統啟動等信息。這些參數是由UIKit的基礎設施解析,否則可以忽略不計。該參數一般不會修改
principalClassName 這個參數標識了應用程序的類的名稱(該類必須繼承自UIApplication類)。這是負責運行應用程序的類。建議為這個參數傳nil。如果principalClassName是nil,那么它的值將從Info.plist去獲取,如果Info.plist沒有,則默認為UIApplication。principalClass這個類除了管理整個程序的生命周期之外什么都不做,它只負責監聽事件然后交給delegateClass去做。該參數一般使用nil
delegateClassName delegateClass是應用程序類的代理類。應用程序的代理負責管理系統和你的代碼之間的高層次的互動。
    1. 程序完成加載
      我們一般會在這里進行一些初始化配置,例如創建window
- [AppDelegate application:didFinishLaunchingWithOptions:]
    1. 創建window窗口
      我們所有的畫面最終都會顯示在該窗口上,makeKeyAndVisible是window顯示的關鍵。
_window = UIWindow.new;
_window.backgroundColor = [UIColor whiteColor];
[_window makeKeyAndVisible];
    1. 程序被激活
      最后該方法會被調用,宣布程序處于激活狀態。
- [AppDelegate applicationDidBecomeActive:]

2. AppDelegate

AppDelegate類有以下常用的函數,這里是我們與系統進行交互的場所,一般在這里創建視圖以及監聽部分設備狀態。

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"%s",__func__);
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
     NSLog(@"%s",__func__);
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
   NSLog(@"%s",__func__);
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
   NSLog(@"%s",__func__);
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
  NSLog(@"%s",__func__);
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
     NSLog(@"%s",__func__);
}

- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"%s",__func__);
}

APP狀態更改后會收到一些通知。

// 啟動APP會調用
-[AppDelegate application:didFinishLaunchingWithOptions:]
-[AppDelegate applicationDidBecomeActive:]

// 點擊Home鍵會調用
-[AppDelegate applicationWillResignActive:]
-[AppDelegate applicationDidEnterBackground:]

// APP從后臺返回前臺
-[AppDelegate applicationWillEnterForeground:]
-[AppDelegate applicationDidBecomeActive:]

// 收到內存警告
-[AppDelegate applicationDidReceiveMemoryWarning:]
函數 分析 注意事項
application:willFinishLaunchingWithOptions:
application:didFinishLaunchingWithOptions:
分別是程序首次將要和已經完成啟動時執行,一般在這個函數里創建window對象,將程序內容通過window呈現給用戶。

①檢查啟動選項字典中的內容,查看程序啟動的方式,并做出適當的反應。
②初始化應用程序的關鍵數據結構。
③準備好你的應用程序的窗口和視圖進行顯示。
1. 使用OpenGL ES的應用程序不應該使用這個方法來準備他們的繪圖環境。相反,他們應該推遲到application:DidBecomeActive:方法調用時啟動OpenGL ES繪圖方法。

2. 您的應用程序方法應該總是盡可能為輕量,以減少你的應用程序的啟動時間。應用預期將啟動并初始化自身,并開始處理不到5秒的事件。如果一個應用程序沒有及時完成它的啟動周期,系統會殺死它。因此,有可能你的啟動慢下來(如接入網絡)的任何任務,應在異步輔助線程執行。

3. 當程序啟動到前臺,該系統還會調用applicationDidBecomeActive:方法來完成過渡到前臺。因為這種方法既在啟動時與從后臺過渡到前臺時被調用,使用它來執行所共有的兩個轉變的任何任務。
applicationWillResignActive 程序將要失去Active狀態時調用,比如有電話進來或者按下Home鍵,之后程序進入后臺狀態,對應的applicationWillEnterForeground(即將進入前臺)方法。 該函數里面主要執行操作:

a . 暫停正在執行的任務
b. 禁止計時器
c. 減少OpenGL ES幀率
d. 若為游戲應暫停游戲
applicationDidEnterBackground 該方法用來:

a. 釋放共享資源
b. 保存用戶數據(寫到硬盤)
c. 作廢計時器
d. 保存足夠的程序狀態以便下次修復;
applicationWillEnterForeground 這個方法用來: 撤銷applicationWillResignActive中做的改變。
applicationDidBecomeActive 若程序之前在后臺,在此方法內刷新用戶界面
applicationWillTerminate 程序即將退出時調用。記得保存數據,如applicationDidEnterBackground方法一樣。

3. 啟動優化

如果你剛剛啟動過App,這時候App的啟動所需要的數據仍然在緩存中,再次啟動的時候稱為熱啟動。如果設備剛剛重啟,然后啟動App,這時候稱為冷啟動。

啟動時間在小于400ms是最佳的,因為從點擊圖標到顯示Launch Screen,到Launch Screen消失這段時間是400ms。啟動時間不可以大于20s,否則會被系統殺掉。

以main函數作為分水嶺,啟動時間其實包括了兩部分:main函數之前和main函數到第一個界面的viewDidAppear:。不過一般情況下都是耗時都產生在自己的代碼,優先考慮優化main之后的過程。

優化這些初始化的核心思想就是:能延遲初始化的盡量延遲初始化,不能延遲初始化的盡量放到后臺初始化。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容