iOS main()之前都發(fā)生了什么

這篇文章首發(fā)在公司微信技術(shù)公眾號(hào):京東金融技術(shù)說

「摘要」每個(gè)iOS app從點(diǎn)擊啟動(dòng)到首頁面加載渲染完成惰赋,雖然時(shí)間比較短暫番川,但系統(tǒng)進(jìn)行了不少重要的操作船庇,比如會(huì)加載100-400個(gè)支撐app后續(xù)運(yùn)行的dylib鞭盟。本文從Dylib、Mach-O等基本運(yùn)行時(shí)文件說起,介紹了iOS 的Virtual Memory劈彪,以及從exec()到main()之間經(jīng)歷的主要階段:Dylibs Loading竣蹦、Rebasing、Binding沧奴、ObjC Runtime痘括、Initializer。然后從理論升級(jí)到實(shí)踐滔吠,針對(duì)啟動(dòng)中的各個(gè)環(huán)節(jié)分析如何更進(jìn)一步提升app的啟動(dòng)速度纲菌。

一、Mach-O

相關(guān)術(shù)語說明(文件類型)

Executable—iOS 應(yīng)用程序的主要二進(jìn)制文件

Dylib—Dynamic library疮绷,動(dòng)態(tài)庫 (其他平臺(tái)中叫DSO 或者 DLL)

Bundle—可以看做是不能鏈接的Dylib,只能使用dlopen()來加載,就像插件一樣

Image— 泛指 Executable,Dylib或者Bundle

Framework—包含Dylib翰舌、圖片等資源、頭文件(.h)的特定結(jié)構(gòu)的目錄文件

1冬骚、Mach-O Image

Image 被分割成多個(gè) segment灶芝。segment一般用大寫字母來命名,其大小一般是 page 大小的整數(shù)倍(arm64環(huán)境下1page是16KB唉韭,其它環(huán)境下是4KB)。如下圖犯犁,一個(gè)Image通常由三個(gè)segment組成属愤,分別是__TEXT(3page)、__DATA(1page)酸役、__LINKEDIT(1page)住诸。

Section 是Segment所包含的一部分,一般用小寫字母命名涣澡。Section的大小沒有是page整數(shù)倍的要求贱呐,但它是不可被覆蓋的,同時(shí)它也是被編譯器忽略的入桂。如下圖奄薇,__TEXT是文件的開始,包含了Machheader抗愁,主要有對(duì)應(yīng)硬件環(huán)境信息的說明馁蒂,只讀的常量(如C字符串)。__DATA是可讀亦可寫的蜘腌,主要包含一些全局變量沫屡,靜態(tài)變量。__LINKEDIT并不包含全局變量的函數(shù)/方法(function)撮珠,而是包含函數(shù)/方法(function)的一些信息沮脖,比如其名字,地址等等。

2勺届、Mach-O universal file(通常稱為胖二進(jìn)制文件)

由于硬件在快速的升級(jí)換代驶俊,之前是32位的機(jī)器,現(xiàn)在已經(jīng)有好多的64位機(jī)器涮因。并且废睦,每一代的iPhone,其CPU架構(gòu)都不完全相同养泡,有i386嗜湃,armv7,armv7s澜掩,arm64购披。為了讓同一份代碼可以部署到不同的環(huán)境下,就需要一種通用的可執(zhí)行文件了—Mach-O universal file肩榕。如下圖刚陡,將armv7s和arm64下的Mac-O文件合并,形成一個(gè)新的Mach-O universal file株汉,其包含了一個(gè)Fat Header筐乳。Fat Header中包含了所支持的架構(gòu)列表以及其在文件中的偏移量(offsets)。

二乔妈、Virtual Memory

Virtual Memory 是一個(gè)中間層蝙云,用于將每個(gè)進(jìn)程(process)的邏輯地址空間映射到物理RAM上(以page的粒度)。VM有如下的特點(diǎn):

某些邏輯地址沒有映射到具體物理RAM上路召,內(nèi)核訪問該地址時(shí)勃刨,會(huì)出現(xiàn)Pagefault

多個(gè)process可以映射到相同的一個(gè)page 上

可以實(shí)現(xiàn)對(duì)文件的延遲讀取

對(duì)于共享的page可以進(jìn)行 Copy-On-Write

Copy-On-Write 會(huì)導(dǎo)致Dirtypage,所以需要pageclean

RAM的權(quán)限(rwx)與映射權(quán)限的聯(lián)系

1股淡、Mach-O ImageLoading

結(jié)合上面的Dylib和VM的基本知識(shí)身隐,Mach-O Image 的加載過程可通過下圖來展示出來:

兩個(gè)重要的安全因素對(duì)Image Loading的影響:

一個(gè)是 ASLR:(address space layout randomization),為了防止Image 每次都被加載到同一個(gè)物理RAM上而被惡意利用,Image每次分配到的地址偏移量是隨機(jī)的唯灵。

另一個(gè)是 code sign:這個(gè)使用XCode編譯打包過的人應(yīng)該非常熟悉了贾铝,和編譯期不同的是,每一個(gè)page的Mach-O都有自己的簽名埠帕,這些簽名信息保存在__LINKEDIT中忌傻。

三、從exex() 到 main()

exec()是一個(gè)系統(tǒng)級(jí)別的調(diào)用搞监,當(dāng)點(diǎn)擊appicon或者不同app間切換時(shí)水孩,會(huì)觸發(fā)它。對(duì)于該app琐驴,exec()主要進(jìn)行如下的操作:

將app映射到一個(gè)隨機(jī)的地址空間

app的起始地址是隨機(jī)的

將起始地址和0x000000之間的地址空間俘种,對(duì)該app標(biāo)記為不可操作(不可讀秤标,不可寫,不可執(zhí)行)

捕獲空指針的使用

捕獲指針缺失的錯(cuò)誤

1宙刘、加載Dylibs

接下來苍姜,Dyld(dynamicloader)會(huì)開始加載app中的dylib,主要步驟如下:

從胖二進(jìn)制文件中的header中解析app所依賴的dylibs列表

找到該環(huán)境下所需的mach-O 文件

打開每一個(gè)dylib,讀取其頭部悬包,進(jìn)行驗(yàn)證衙猪,看是否是mach-O格式,然后找到代碼簽名布近,并將代碼簽名注冊(cè)到內(nèi)核

調(diào)用mmap()垫释,對(duì)所有的segment進(jìn)行映射

現(xiàn)在,所有的一級(jí)dylib都加載了撑瞧,但是有的dylib依賴于其他dylib棵譬,甚至同一dylib被多個(gè)dylib依賴。所以需要進(jìn)行遞歸的加載预伺,一直到所有的都加載完成

通常一個(gè)app會(huì)加載100-400個(gè)dylib订咸,這其中大部分是操作系統(tǒng)的,系統(tǒng)本身已經(jīng)對(duì)這些dylib的加載進(jìn)行了優(yōu)化

上述過程如下圖:

2、Rebasing 和 Binding

每個(gè)dylib分配到的地址空間也是隨機(jī)的酬诀,也就是其起始地址會(huì)不斷的變化脏嚷,或者說是滑動(dòng)(slide)。

Rebasing就是在發(fā)生變化時(shí)瞒御,對(duì)內(nèi)部的指針按照新的偏移量進(jìn)行校正然眼。而binding是對(duì)指向外部的指針進(jìn)行校正。校正后的地址信息都是保存在__LINKEDIT中葵腹。

可以使用 dyldinfo 命令來查看任意 dylib 的Rebasing和Binding信息:

2、通知 ObjC Runtime

大多數(shù)的ObjC設(shè)置都通過Rebasing和Binding完成了屿岂,比如注冊(cè)O(shè)bjC class 聲明践宴,將Category中聲明的方法插入到方法列表中。之后爷怀,Runtime啟動(dòng)并開始初始化操作

3阻肩、Initializer

如果是C++,靜態(tài)對(duì)象的initializer開始執(zhí)行运授。對(duì)于ObjC烤惊,+load 方法被調(diào)用。其她的+方法也開始被調(diào)用吁朦。由于+方法之間會(huì)存在繼承調(diào)用關(guān)系柒室,所以,整個(gè)+方法的被調(diào)用順序是由下向上的逗宜。

接下來雄右,app中的main()方法會(huì)被調(diào)用空骚,main()被調(diào)用前經(jīng)歷的TimeLine:

四、從理論到實(shí)踐-如何提升啟動(dòng)時(shí)間擂仍?

1囤屹、怎樣的速度算快

啟動(dòng)速度應(yīng)當(dāng)比閃屏動(dòng)畫速度快,不同的硬件設(shè)備逢渔、系統(tǒng)環(huán)境下肋坚,啟動(dòng)速度都不相同,400ms是一個(gè)比較合理的目標(biāo)肃廓,啟動(dòng)時(shí)間不能超過20 秒智厌,超過了會(huì)被系統(tǒng)Kill掉

需要在低性能設(shè)備上做測試。

2亿昏、回顧一下整個(gè)啟動(dòng)堆棧

Parse ?images

Map ?images

Rebase ?images

Bind ?images

Run ?imageinitializers

Call ?main()

Call ?UIApplicationMain()

CallapplicationWillFinishLaunching

Call applicationdidFinishLaunchingWithOptions(iOSer 寫應(yīng)用程序的入口)

3峦剔、冷啟動(dòng)、熱啟動(dòng)角钩,檢測啟動(dòng)時(shí)間

熱啟動(dòng)是指app啟動(dòng)之前吝沫,app執(zhí)行文件和相關(guān)的數(shù)據(jù)已經(jīng)在緩存中。冷啟動(dòng)則相反递礼,內(nèi)核緩存中沒有任何數(shù)據(jù)惨险,所以對(duì)于冷啟動(dòng)來說,時(shí)間更為重要脊髓。冷啟動(dòng)的使用場景是辫愉,更新系統(tǒng)后第一次打開app,或者很長時(shí)間沒有使用過app将硝,再次打開恭朗。

之前,檢測main()之前的時(shí)間消耗是非常困難的依疼,在最新的iOS10系統(tǒng)痰腮,嵌入了新功能,可以方便的測試啟動(dòng)時(shí)間:


4律罢、dylib 加載階段

盡量減少dylib 的數(shù)量膀值,可以合并多個(gè)dylib,或者將代碼直接放在executable中误辑。

使用dlopen來延遲加載沧踏,雖然使用dlopen會(huì)導(dǎo)致額外的開銷,甚至大于在啟動(dòng)時(shí)加載的巾钉,但它是延遲的翘狱。(注:近一年內(nèi)Hot patch非常火熱砰苍,由于Hot patch本身的機(jī)制和安全問題盒蟆,導(dǎo)致蘋果開始封殺踏烙,dlopen就被加入了禁用名單了)

從架構(gòu)的角度,把a(bǔ)pp中的一些共用功能以dylib的形式模塊化历等,可以降低耦合讨惩,提升開發(fā)效率。但是過多的話寒屯,會(huì)影響啟動(dòng)時(shí)間荐捻。

5、Rebasing和Binding階段

Rebasing和Binding階段寡夹,會(huì)產(chǎn)生IO和一些計(jì)算消耗处面,可以通過如下方案優(yōu)化:

減少__DATA 中的指針數(shù)量可以減少處理時(shí)間

減少Objective-C的元數(shù)據(jù),如Classes菩掏,selectors魂角,categores

減少C++中的虛方法

使用swift 的 struct

6、Initializer階段

ObjC +load方法替換成+initialize 形式的:C/C++中的__attribute__((constructor)) 換成 dispatch_once()智绸,pthread_once(),std:once()

不要在initialize中使用 dlopen

不要在initialize中創(chuàng)建線程

使用swift


整理自WWDC 2016 - Session 406

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末野揪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瞧栗,更是在濱河造成了極大的恐慌斯稳,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迹恐,死亡現(xiàn)場離奇詭異挣惰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)殴边,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門憎茂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锤岸,你說我怎么就攤上這事竖幔。” “怎么了能耻?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長亡驰。 經(jīng)常有香客問我晓猛,道長,這世上最難降的妖魔是什么凡辱? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任戒职,我火速辦了婚禮,結(jié)果婚禮上透乾,老公的妹妹穿的比我還像新娘洪燥。我一直安慰自己磕秤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布捧韵。 她就那樣靜靜地躺著市咆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪再来。 梳的紋絲不亂的頭發(fā)上蒙兰,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音芒篷,去河邊找鬼搜变。 笑死,一個(gè)胖子當(dāng)著我的面吹牛针炉,可吹牛的內(nèi)容都是我干的挠他。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼篡帕,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼殖侵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赂苗,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤愉耙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拌滋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朴沿,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年败砂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赌渣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昌犹,死狀恐怖坚芜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斜姥,我是刑警寧澤鸿竖,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站铸敏,受9級(jí)特大地震影響缚忧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杈笔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一闪水、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蒙具,春花似錦球榆、人聲如沸朽肥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衡招。三九已至,卻和暖如春右钾,著一層夾襖步出監(jiān)牢的瞬間蚁吝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工舀射, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窘茁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓脆烟,卻偏偏與公主長得像山林,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邢羔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容