iOS:程序main函數之前發生了什么

我是前言

一個 iOS App 的 main 函數位于 main.m 中,這是我們熟知的程序入口。但對 objc 了解更多之后發現,程序在進入我們的 main 函數前已經執行了很多代碼,比如熟知的 + load 方法等。本文將跟隨程序執行順序,刨根問底,從 dyldruntime ,看看 main 函數之前都發生了什么。

從dyld開始

動態鏈接庫

iOS 中用到的所有系統 framework 都是動態鏈接的,類比成插頭和插排,靜態鏈接的代碼在編譯后的靜態鏈接過程就將插頭和插排一個個插好,運行時直接執行二進制文件;而動態鏈接需要在程序啟動時去完成“插插銷”的過程,所以在我們寫的代碼執行前,動態連接器需要完成準備工作。

這個是在 Xcode 中看到的 Link 列表:

image

這些 framework 將會在動態鏈接過程中被加載,另外還有隱含 link 的 framework,可以測試出來:先找到可執行文件,我這里叫 TestMain 的工程,模擬器路徑下找到 TestMain.app,可執行文件默認同名,再通過 otool命令:

 otool -L TestMain

-L 參數打印出所有 link 的 framework(去掉了版本信息如下)

TestMain:
 /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
 /System/Library/Frameworks/UIKit.framework/UIKit
 /System/Library/Frameworks/Foundation.framework/Foundation
 /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
 /usr/lib/libobjc.A.dylib
 /usr/lib/libSystem.dylib

除了多了的CoreGraphics(被 UIKit 依賴)外,有兩個默認添加的 lib:libobjc 即 objc 和 runtime,libSystem 中包含了很多系統級別 lib,列幾個熟知的:

  • libdispatch ( GCD )
  • libsystem_c ( C語言庫 )
  • libsystem_blocks ( Block )
  • libcommonCrypto ( 加密庫,比如常用的 md5 函數 )

這些 lib 都是dylib格式(如 windows 中的 dll ),系統使用動態鏈接有幾點好處:

  • 代碼共用:很多程序都動態鏈接了這些 lib,但它們在內存和磁盤中中只有一份
  • 易于維護:由于被依賴的 lib 是程序執行時才 link 的,所以這些 lib 很容易做更新,比如libSystem.dyliblibSystem.B.dylib 的替身,哪天想升級直接換成 libSystem.C.dylib 然后再替換替身就行了
  • 減少可執行文件體積:相比靜態鏈接,動態鏈接在編譯時不需要打進去,所以可執行文件的體積要小很多

dyld

dyld(the dynamic link editor),Apple 的動態鏈接器,系統 kernel 做好啟動程序的初始準備后,交給 dyld 負責,援引并翻譯《 Mike Ash 這篇 blog 》對 dyld 作用順序的概括:

  1. 從 kernel 留下的原始調用棧引導和啟動自己
  2. 將程序依賴的動態鏈接庫遞歸加載進內存,當然這里有緩存機制
  3. non-lazy 符號立即 link 到可執行文件,lazy 的存表里
  4. Runs static initializers for the executable
  5. 找到可執行文件的 main 函數,準備參數并調用
  6. 程序執行中負責綁定 lazy 符號、提供 runtime dynamic loading services、提供調試器接口
  7. 程序main函數 return 后執行 static terminator
  8. 某些場景下 main 函數結束后調 libSystem 的 _exit 函數

得益于 dyld 是開源的,github 地址,我們可以從源碼一探究竟。

一切源于dyldStartup.s這個文件,其中用匯編實現了名為__dyld_start的方法,匯編太生澀,它主要干了兩件事:

  1. 調用dyldbootstrap::start()方法(省去參數)
  2. 上個方法返回了 main 函數地址,填入參數并調用 main 函數

這個步驟隨手就能驗證出來,設置一個符號斷點斷在_objc_init

image

這個函數是runtime的初始化函數,后面會提到。程序運行在很早的時候斷住,這時候看調用棧:

image

看到了棧底的dyldbootstrap::start()方法,繼而調用了dyld::_main()方法,其中完成了剛才說的遞歸加載動態庫過程,由于libSystem默認引入,棧中出現了libSystem_initializer的初始化方法。

ImageLoader

當然這個 image 不是圖片的意思,它大概表示一個二進制文件(可執行文件或 so 文件),里面是被編譯過的符號、代碼等,所以 ImageLoader 作用是將這些文件加載進內存,且每一個文件對應一個ImageLoader實例來負責加載。

兩步走:

  1. 在程序運行時它先將動態鏈接的 image 遞歸加載 (也就是上面測試棧中一串的遞歸調用的時刻)
  2. 再從可執行文件 image 遞歸加載所有符號

當然所有這些都發生在我們真正的main函數執行前。


runtime 與 +load

剛才講到 libSystem 是若干個系統 lib 的集合,所以它只是一個容器 lib 而已,而且它也是開源的,里面實質上就一個文件,init.c,由 libSystem_initializer 逐步調用到了 _objc_init,這里就是 objc 和 runtime 的初始化入口。

除了 runtime 環境的初始化外,_objc_init中綁定了新 image 被加載后的 callback:

dyld_register_image_state_change_handler(
dyld_image_state_bound, 1, &map_images);
dyld_register_image_state_change_handler(
dyld_image_state_dependents_initialized, 0, &load_images);

可見 dyld 擔當了 runtimeImageLoader 中間的協調者,當新 image 加載進來后交由 runtime 大廚去解析這個二進制文件的符號表和代碼。繼續上面的斷點法,斷住神秘的 +load 函數:

image

清楚的看到整個調用棧和順序:

  1. dyld 開始將程序二進制文件初始化
  2. 交由 ImageLoader 讀取 image,其中包含了我們的類、方法等各種符號
  3. 由于 runtime 向 dyld 綁定了回調,當 image 加載到內存后,dyld 會通知 runtime 進行處理
  4. runtime 接手后調用 map_images 做解析和處理,接下來 load_images 中調用 call_load_methods 方法,遍歷所有加載進來的 Class,按繼承層級依次調用 Class 的 +load 方法和其 Category 的 +load 方法

至此,可執行文件中和動態庫所有的符號(Class,Protocol,Selector,IMP,…)都已經按格式成功加載到內存中,被 runtime 所管理,再這之后,runtime 的那些方法(動態添加 Class、swizzle 等等才能生效)

關于 +load 方法的幾個 QA

Q: 重載自己 Class 的 +load 方法時需不需要調父類?
A: runtime 負責按繼承順序遞歸調用,所以我們不能調 super

Q: 在自己 Class 的 +load 方法時能不能替換系統 framework(比如 UIKit)中的某個類的方法實現
A: 可以,因為動態鏈接過程中,所有依賴庫的類是先于自己的類加載的

Q: 重載 +load 時需要手動添加 @autoreleasepool 么?
A: 不需要,在 runtime 調用 +load 方法前后是加了 objc_autoreleasePoolPush()objc_autoreleasePoolPop() 的。

Q: 想讓一個類的 +load 方法被調用是否需要在某個地方 import 這個文件
A: 不需要,只要這個類的符號被編譯到最后的可執行文件中,+load 方法就會被調用(Reveal SDK 就是利用這一點,只要引入到工程中就能工作)


簡單總結

整個事件由 dyld 主導,完成運行環境的初始化后,配合 ImageLoader 將二進制文件按格式加載到內存,
動態鏈接依賴庫,并由 runtime 負責加載成 objc 定義的結構,所有初始化工作結束后,dyld 調用真正的 main 函數。
值得說明的是,這個過程遠比寫出來的要復雜,這里只提到了 runtime 這個分支,還有像 GCDXPC等重頭的系統庫初始化分支沒有提及(當然,有緩存機制在,它們也不會玩命初始化),總結起來就是 main 函數執行之前,系統做了茫茫多的加載和初始化工作,但都被很好的隱藏了,我們無需關心。


孤獨的 main 函數

當這一切都結束時,dyld 會清理現場,將調用?;貧w,只剩下:


image

孤獨的 main 函數,看上去是程序的開始,確是一段精彩的終結


References

https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html
http://newosxbook.com/articles/DYLD.html
http://docstore.mik.ua/orelly/unix3/mac/ch05_02.htm
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dyld.1.html


聲明

此文引用自iOS程序 main 函數之前發生了什么,自從業以來,從孫源老師那學到了很多,在此表示由衷的感謝!

其他拓展

dyld:dyld詳解

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

推薦閱讀更多精彩內容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • 你以為四川是辣是火熱是五光十色是寶馬雕車香滿路 生于斯長于斯不過只流連了大半個平原 春熙路涌動的人潮 太古里的所所...
    心一呀閱讀 207評論 0 1
  • 1、芭芭拉.里斯曼對這個觀點感到好奇,決定進一步研究它。她的發現挑戰了來傳統智慧。顯然,負責照顧孩子或者年邁父...
    鄧潔兒閱讀 151評論 0 1