前言
這裡主要剖析一下一個App從點選圖示,到展現首頁的整個過程。
App是如何啟動的
按順序劃分
- 載入可執行檔案(讀取Mach-O)
- 載入動態庫(Dylib)
- Rebase & Bind
- Objc
- Initializers
--------- main() ---------
- 執行AppDelegate的代理方法(如:
didFinishLaunchingWithOptions
)。 - 根據業務註冊SDK,獲取資料庫資料等。
- 初始化Windows,初始化ViewController。
載入可執行檔案(讀取Mach-O)
Apple作業系統使用dyld載入可執行檔案。
dyld全程為dynomic loader,作用是載入一個程式所需要的Image,在opensource-apple可以找到它的開原始碼。
載入動態庫(Dylib)
dyld讀取完Mach-O的Header和Load Commands後,就會找到可執行檔案的依賴動態庫。接著dyld會將所依賴的動態庫載入到記憶體中。這是一個遞迴的過程,依賴的動態庫可能還會依賴別的動態庫,所以dyld會遞迴每個動態庫,直至所有的依賴庫都被載入完畢。
載入後的動態庫會被快取到dyld shared cache中,提高讀取效率。
簡單的說下Mach-O,簡單的可以分為三個部分,Header,Load Commands,Segment Data。
Header中包含的是可執行檔案的CPU架構,Load Commands的數量和佔用空間。
Load Commands中包含的是Segment的Header與記憶體分佈,以及依賴動態庫的版本和Path等。
Segment Data就是Segment彙編程式碼的實現,每段Segment的記憶體佔用大小都是分頁頁數的整數倍。
Rebase & Bind
這兩個過程合在一起說,是因為他們之間的工作是相互補充的。
Apple為了解決應用安全,用到了ASLR(Address space layout randomization 地址空間佈局隨機化)和Code Sign。
App被啟動後,會被對映到虛擬記憶體中,這樣App在這個空間中就有了一個起始地址,但這個起始地址是固定的。ASLR能使這個起始地址隨機化,這項技術可以防止攻擊者通過初始地址+偏移量的方法找到函式的記憶體地址。
Code Sign就是簽名,在進行加密的時候,會對每一個Page(這裡指的是Segment Data)都進行加密,當dyld進行載入的時候,會對每一個Page都進行獨立的驗證。
Mach-O中採用了PIC(Position Independent Code 地址無關程式碼),大當我們在呼叫函式時,會在__Data段中建立一個指向該函式的指標,通過這個指標來間接呼叫。
Mach-O中有很多符號,有些指向當前Mach-O的(我們為App編寫的程式碼),有些指向其他DyLib(依賴的動態庫)。
Rebase的作用是重新修正指向當前Mach-O指標的指向,因為上面提到的ASLR將地址隨機化,起始地址不在是固定的,重新修復後,App才能正常執行。
Bind的作用是重新修復外部指標的指向,這個過程會根據字串匹配的方式來查詢符號表,比起Rebase會略慢(這裡fishhook的實現基礎,它在dyld繫結C庫的時候進行了hook)。
Objc
因為Objective C的動態特性,所以在Main函式執行之前,需要把類資訊註冊到一個全域性Table中。同時,Category的方法也會被註冊到對應類中,Category中的同名方法實現,會根據編譯順序,被最後一個編譯的Category實現所覆蓋。同時還會做Selector的唯一性檢測。
Initializers
這個階段是包含必要的初始化。
- +load
- C/C++靜態初始化物件和標記有__attribute__(constructor)的方法
這裡區分下+load方法與+Initialize方法,前者是在類載入時呼叫的,後者是在類第一次收到message之前呼叫的。
main()方法之後的事
這裡就不做展開了,都是我們親手寫的程式碼。
Dyld 3
以上我們介紹了dyld2的載入方式,在2017WWDC,Apple推出了Dyld3。
Dyld2是從程式開始時才開始執行的,而Dyld3則將Dyld2的一些過程進行了分解。
Dyld3分為out-of-process,和in-process。
out-process會做:
- 分析Mach-O Headers
- 分析以來的動態庫
- 查詢需要的Rebase和Bind的符號
- 將上面的分析結果寫入快取。
in-process會做:
- 讀取快取的分析結果
- 驗證分析結果
- 載入Mach-O檔案
- Rebase&Bind
- Initializers
使用了Dyld3後,App的啟動速度會進一步提高
啟動階段的優化建議
- 減少動態庫的數量,推薦使用系統庫。
- 減少類和方法的數量。
- 減少初始化函式。
- 儘量使用Swift。
- Swift沒有初始化器。
- Swift不允許特定型別的未對齊資料結構。
- Swift程式碼更精簡。
想要了解Dyld的同學,可以看看這篇文章App 啟動流程以及優化 WWDC 2017