1. 前言
現(xiàn)在的互聯(lián)網(wǎng)行業(yè)掸读,是一天比一天卷澡罚,除了底層是必考點(diǎn)了,還有關(guān)于APP
的性能優(yōu)化也是面試常問的點(diǎn)催式。
在優(yōu)化之前必須要對
應(yīng)用程序加載
的流程熟悉哺窄,那么本次博文就對dyld
進(jìn)行底層的初步探索分析坷襟。
2. 程序加載原理
2.1 代碼編譯過程
我們都知道代碼編寫完成抱婉,必須通過編譯器編譯才能變成可以執(zhí)行的文件。
程序的執(zhí)行,是把可執(zhí)行的文件,加載到內(nèi)存中去執(zhí)行的恕齐,這個(gè)可執(zhí)行的文件(
Mach-O
)的運(yùn)行必須依賴很多的庫(.a
/.lib
/.so
),庫是可執(zhí)行的二進(jìn)制文件范删,是能夠被加載到內(nèi)存中去的。這些庫,可以分為靜態(tài)庫
和動(dòng)態(tài)庫
。
2.2 靜態(tài)庫和動(dòng)態(tài)庫
- 靜態(tài)庫: 例如
.a
和.framework
。靜態(tài)庫鏈接時(shí),會被完整地復(fù)制到可執(zhí)行文件中,被使用到了多次,就會復(fù)制多份,這樣就有多份拷貝很冗余,鏈接時(shí)間長了欢摄,還浪費(fèi)了內(nèi)存空間害捕。 - 動(dòng)態(tài)庫:例如
.dylib
和.framework
盾沫。動(dòng)態(tài)庫鏈接時(shí)凸舵,只會存在一份,并不會復(fù)制多份,在內(nèi)存中共享這一份,系統(tǒng)只加載一次,誰有用到了就去找這一份郊艘,減少了程序的體積大小,可以節(jié)省時(shí)間和內(nèi)存空間。
靜態(tài)庫和動(dòng)態(tài)庫
靜態(tài)庫都好理解顶掉,那么動(dòng)態(tài)庫在程序中是怎么加載到內(nèi)存呢茬贵?系統(tǒng)是通過怎樣的方式來鏈接的呢螟左?這就用到了一個(gè)工具,也就是博文開頭提到的dyld
(the dynamic link editor
)動(dòng)態(tài)鏈接器,
2.3 dyld 動(dòng)態(tài)鏈接器
dyld
是iOS操作系統(tǒng)的一個(gè)重要組成部分,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后暇番,會交由dyld
負(fù)責(zé)余下的工作嗤放。
dyld
的作用:加載各個(gè)庫,也就是image
鏡像文件壁酬,由dyld
從內(nèi)存中讀到表中斤吐,加載主程序,link
鏈接各個(gè)動(dòng)靜態(tài)庫厨喂,進(jìn)行主程序的初始化工作和措。
上面這個(gè)圖是
dyld
的加載工作流程圖,圖中簡單的描述了動(dòng)態(tài)庫
的注冊
和動(dòng)態(tài)庫的加載
過程蜕煌,具體分析還得去看底層源碼派阱,那么接下來就去探索分析。
3. 初識dyld
3.1 尋找dyld入口
程序的執(zhí)行我們都知道是從
main
函數(shù)開始的斜纪,那么dyld
在程序的那個(gè)階段執(zhí)行的呢贫母?是在main
函數(shù)執(zhí)行前,還是再main
函數(shù)執(zhí)行后呢盒刚?這我也不得而知腺劣。
啊因块!你這個(gè)博主橘原,奇奇怪怪的,你寫的博文,你會不知道爸憾稀拒名?
這個(gè)嘛,就得探索探索了芋酌!首先建一個(gè)工程將
main.m
改寫如下:
__attribute__((constructor)) void JPFunc(){
printf("來了老弟 : %s \n",__func__);
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSLog(@"這是main函數(shù)打印");
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
然后運(yùn)行程序增显,打印結(jié)果如下:
來了老弟 : JPFunc
dyld初探[37212:516752] 這是main函數(shù)打印
這個(gè)__attribute__((constructor))
是在main
函數(shù)之前執(zhí)行的一個(gè)函數(shù)。
補(bǔ)充:
GNU C
的一大特色就是__attribute__
機(jī)制脐帝。__attribute__
可以設(shè)置函數(shù)屬性(Function Attribute
)同云、變量屬性(Variable Attribute
)和類型屬性(Type Attribute
)。__attribute__
書寫特征是:__attribute__
前后都有兩個(gè)下劃線堵腹,并切后面會緊跟一對原括弧梢杭,括弧里面是相應(yīng)的__attribute__
參數(shù)。__attribute__
語法格式為:__attribute__ ((attribute-list))
在main
函數(shù)執(zhí)行之前確實(shí)是可以執(zhí)行其他函數(shù)的秸滴,那么dyld
到現(xiàn)在好像還沒有相關(guān)線索武契,那么繼續(xù)往下探索。
在main
函數(shù)打上斷點(diǎn)
斷點(diǎn)斷在
main
函數(shù)上荡含,發(fā)現(xiàn)在main
之前還調(diào)用了一個(gè)start
方法咒唆。
點(diǎn)開
start
是一個(gè)libdyld.dylib start
,突然想起網(wǎng)絡(luò)上很流行的一句話释液,歡迎來到德萊聯(lián)盟
全释,libdyld.dylib
和這發(fā)音好像,哈哈误债!
但是通過對start
下符號斷點(diǎn)浸船,斷不住它。說明這不是入口的地方寝蹈,我們知道還有一個(gè)方法+load
李命,這個(gè)是在main
之前必會調(diào)用的方法,那么就可以在Viewcontroller
的寫下+load
方法添加斷點(diǎn)箫老,運(yùn)行程序封字。在控制臺輸入指令bt
,查看調(diào)用堆棧信息:
堆棧信息是一個(gè)棧結(jié)構(gòu)耍鬓,先進(jìn)后出阔籽,所以最底下打印的就是最先執(zhí)行的。所以現(xiàn)在我們已經(jīng)找到
dyld
的入口了牲蜀。
3.2 獲取dyld源碼
_dyld_start
笆制,那么這就涉及到底層源碼了,去蘋果開放的源碼官網(wǎng)opensource
看看dyld源碼
我們研究源碼涣达,就得去看最新的蘋果源碼在辆,畢竟技術(shù)更新迭代很快证薇,最新的才是最流行的,也是最香的开缎,研究起來才有味道棕叫,
dyld
最新的版本是dyld-852林螃,這部分源碼是不能編譯的奕删,但是并不能妨礙我們?nèi)ヌ剿魉D敲次覀儸F(xiàn)在就去打開dyld
這個(gè)牛逼的源碼工程一探究竟吧疗认!
3.3 初探dyld 源碼
全局搜索
_dyld_start完残,
發(fā)現(xiàn)又是匯編,是不是要瘋了昂崧谨设!
莫慌靚仔,穩(wěn)住缎浇,不會匯編沒有關(guān)系扎拣,請耐心往下看!
在匯編里面發(fā)現(xiàn)了一個(gè)重要方法素跺,
dyldbootstrap::start
二蓝,從紅框中的注釋可以知道,會調(diào)用dyldbootstrap::start
這個(gè)C++
函數(shù)指厌,那么就可以去全局搜索下刊愚,看看C++
函數(shù)的命名空間。
從命名空間里面踩验,我們可以找到
start
在
start
函數(shù)里面返回的是dyld::_main
鸥诽,這就nice了,突然就很熟悉箕憾,很親切牡借。
博文篇幅有限,下篇繼續(xù)探索袭异。蓖捶。。扁远。俊鱼。
iOS底層探索之dyld(下):動(dòng)態(tài)鏈接器流程源碼分析
4. 總結(jié)
- 應(yīng)用程序加載到內(nèi)存中運(yùn)行,會通過
dyld
鏈接各種庫 - 庫分為
動(dòng)態(tài)庫
和靜態(tài)庫
-
靜態(tài)庫
有多份拷貝畅买,增加包的大小并闲,程序加載鏈接時(shí)間長,浪費(fèi)了內(nèi)存空間谷羞。 -
動(dòng)態(tài)庫
存在一份帝火,并不會復(fù)制多份溜徙,節(jié)省程序加載鏈接時(shí)間和內(nèi)存空間 - iOS的可執(zhí)行的文件是
Mach-O
- dyld源碼可以去蘋果的開源opensource查看
更多內(nèi)容持續(xù)更新
?? 喜歡就點(diǎn)個(gè)贊吧????
?? 覺得學(xué)習(xí)到了的,可以來一波犀填,收藏+關(guān)注蠢壹,評論 + 轉(zhuǎn)發(fā),以免你下次找不到我?? ??
??歡迎大家留言交流九巡,批評指正图贸,互相學(xué)習(xí)??,提升自我??