iOS面試題:如何優(yōu)化 App 的啟動耗時击纬?

iOS 的 App 啟動主要分為以下步驟:

  • 打開 App腐碱,系統(tǒng)內(nèi)核進行初始化跳轉(zhuǎn)到 dyld 執(zhí)行。這個過程包括這些步驟:1)分配虛擬內(nèi)存空間掉弛;2)fork 進程症见;3)加載 MachO (自身所有的可執(zhí)行 MachO 文件的集合)到進程空間;4)加載動態(tài)鏈接器 dyld 并將控制權交給 dyld 處理殃饿。在這個過程中內(nèi)核會產(chǎn)生 ASLR(Address space layout randomization) 隨機數(shù)值谋作,這個值用于加載的 MachO 起始地址在內(nèi)存中的偏移,隨機的地址可防止 MachO 代碼掃描并被 hack乎芳,提升安全性遵蚜。通過 ASLR 雖然可隨機化各內(nèi)存區(qū)基地址帖池,但無法將程序內(nèi)的代碼段和數(shù)據(jù)段隨機化,如果繞過(bypass) ASLR 依然可進行篡改吭净,就需要結(jié)合 PIE(Position Independent Executable) 共同使用睡汹。與之相似的還有 PIC(Position Independent Code),位置無關代碼寂殉,作用于共享庫代碼囚巴。PIE/PIC 技術需要在編譯階段開啟。顧名思義友扰,PIC 可將程序代碼裝載到任意地址彤叉,這樣就內(nèi)部的指針不能靠固定的絕對地址訪問士败,而通過相對地址指令如 adrp 來獲取代碼和數(shù)據(jù)矢炼。

  • 進入 dyld 動態(tài)鏈接器,它負責將一個 App 處理為一個可運行的狀態(tài)唁奢,包含:

  • 加載 MachO 的依賴庫(這些依賴庫也是 MachO 格式的文件)甚负。dyld 從可執(zhí)行 MachO 文件的依賴開始, 遞歸加載所有依賴的動態(tài)庫柬焕。 動態(tài)庫包括:iOS 中用到的所有系統(tǒng)動態(tài)庫:加載 OC runtime 方法的 libobjc,系統(tǒng)級別的 libSystem(例如 libdispatch(GCD) 和 libsystem_blocks(Block))梭域;其他 App 自己的動態(tài)庫斑举。根據(jù) Apple 的描述,大部分 App 所加載的庫在 100~400 個碰辅。不過 iOS 系統(tǒng)庫已經(jīng)被特殊優(yōu)化過懂昂,如提前加入共享緩存,提前做好地址修正等没宾。

  • Fix-ups(地址修正)凌彬,包括 rebasing 和 binding 等。ASLR + PIE 技術增強了程序的安全性循衰,使得依賴固定地址進行攻擊的方法失效铲敛,但也增加了程序自身的復雜度,MachO 文件的 rebase 和 bind info 等部分以及啟動時的 fix-ups 地址修正階段就是配合它而產(chǎn)生的会钝。

  • ObjC 環(huán)境配置伐蒋。經(jīng)過了 MachO 程序和依賴庫的加載以及地址修正之后,dyld 所做的大部分事情已經(jīng)完成了迁酸。在這一階段先鱼,dyld 開始對主程序的依賴庫進行初始化工作,而初始化的執(zhí)行部分會回調(diào)到依賴庫內(nèi)部執(zhí)行奸鬓,如 ObjC 的運行時環(huán)境所在的 libobjc.A.dylib 以及 libdispatch.dylib 等焙畔。ObjC Setup 的過程,主要是對 ObjC 數(shù)據(jù)進行關聯(lián)注冊:1)dyld 將主程序 MachO 基址指針和包含的 ObjC 相關類信息傳遞到 libobjc串远;2)ObjC Runtime 從 __DATA 段中獲取 ObjC 類信息宏多,由于 ObjC 是動態(tài)語言儿惫,可以通過類名獲取其實例,所以 Runtime 維護了一個映射所有類的全局類名表伸但。當加載的數(shù)據(jù)包含了類的定義肾请,類的名字就需要注冊到全局表中;3)獲取 protocol更胖、category 等類相關屬性并與對應類進行關聯(lián)铛铁;4)ObjC 的調(diào)用都是基于 selector 的,所以需要對 selector 全局唯一性進行處理函喉。以上步驟由 dyld 啟動 libSystem.dylib 統(tǒng)一對基礎庫進行調(diào)用執(zhí)行避归,這里面就包含了 libobjc 的 Runtime荣月,同時 Runtime 會在 dyld 綁定回調(diào)管呵,當 dyld 處理完相關數(shù)據(jù)后就會調(diào)用 ObjC Runtime 執(zhí)行 Setup 工作。

  • 執(zhí)行各模塊初始化器哺窄。從這一步就開始接近上(業(yè)務)層:1)通過 ObjC Runtime 在 dyld 注冊的通知捐下,當 MachO 鏡像準備完畢后,dyld 會回調(diào)到 ObjC 中執(zhí)行 +load() 方法萌业,包括以下步驟:a)獲取所有 non-lazy class 列表坷襟;b)按繼承以及 category 的順序?qū)㈩惻湃氪虞d列表;c)對待加載列表中的類進行方法判斷并調(diào)用 +load() 方法生年。2)執(zhí)行 C/C++ 初始化構造器婴程,如通過 attribute((constructor)) 注解的函數(shù)。3)如果包含 C++抱婉,則 dyld 同樣會回調(diào)到 libc++ 庫中對全局靜態(tài)變量档叔、隱式初始化等進行調(diào)用。

  • 查找并跳轉(zhuǎn)到 main() 函數(shù)入口蒸绩。到了最后衙四,dyld 回到 Load command,找到 LC_MAIN患亿,拿到 entryoff 再加上 MachO 在內(nèi)存的加載首地址(首地址就是內(nèi)核傳來的 slide 偏移)就得到了 main() 的入口地址传蹈,從而進入我們顯式的程序邏輯。

進入 main() -> UIApplicationMain -> 初始化回調(diào) -> 顯示UI步藕。

iOS 的 App 啟動時長大概可以這樣計算:

t(App 總啟動時間) = t1(main 調(diào)用之前的加載時間) + t2(main 調(diào)用之后的加載時間)惦界。

t1 = 系統(tǒng) dylib(動態(tài)鏈接庫)和自身 App 可執(zhí)行文件的加載。

t2 = main 方法執(zhí)行之后到 AppDelegate 類中的 application:didFinishLaunchingWithOptions:方法執(zhí)行結(jié)束前這段時間咙冗,主要是構建第一個界面沾歪,并完成渲染展示。

在 t1 階段加快 App 啟動的建議:

  • 盡量使用靜態(tài)庫乞娄,減少動態(tài)庫的使用瞬逊,動態(tài)鏈接比較耗時显歧。
  • 如果要用動態(tài)庫,盡量將多個 dylib 動態(tài)庫合并成一個确镊。
  • 盡量避免對系統(tǒng)庫使用 optional linking士骤,如果 App 用到的系統(tǒng)庫在你所有支持的系統(tǒng)版本上都有,就設置為 required蕾域,因為 optional 會有些額外的檢查拷肌。
  • 減少 Objective-C Class、Selector旨巷、Category 的數(shù)量巨缘。可以合并或者刪減一些 OC 類采呐。
  • 刪減一些無用的靜態(tài)變量若锁,刪減沒有被調(diào)用到或者已經(jīng)廢棄的方法。
  • 將不必須在 +load 中做的事情盡量挪到+initialize中斧吐,+initialize 是在第一次初始化這個類之前被調(diào)用又固,+load 在加載類的時候就被調(diào)用。盡量將+load里的代碼延后調(diào)用煤率。
  • 盡量不要用 C++ 虛函數(shù)仰冠,創(chuàng)建虛函數(shù)表有開銷。
  • 不要使用 __atribute__((constructor))將方法顯式標記為初始化器蝶糯,而是讓初始化方法調(diào)用時才執(zhí)行洋只。比如使用 dispatch_once()pthread_once()std::once()昼捍。
  • 在初始化方法中不調(diào)用 dlopen()识虚,dlopen()有性能和死鎖的可能性。
  • 在初始化方法中不創(chuàng)建線程端三。

在 t2 階段加快 App 啟動的建議:

  • 盡量不要使用 xib/storyboard舷礼,而是用純代碼作為首頁 UI。
  • 如果要用 xib/storyboard郊闯,不要在 xib/storyboard 中存放太多的視圖妻献。
  • application:didFinishLaunchingWithOptions:里的任務盡量延遲加載或懶加載。
  • 不要在 NSUserDefaults 中存放太多的數(shù)據(jù)团赁,NSUserDefaults 是一個 plist 文件育拨,plist 文件被反序列化一次。
  • 避免在啟動時打印過多的 log欢摄。
  • 少用 NSLog熬丧,因為每一次 NSLog 的調(diào)用都會創(chuàng)建一個新的 NSCalendar 實例。
  • 每一段 SQLite 語句都是一個段被編譯的程序怀挠,調(diào)用 sqlite3_prepare 將編譯 SQLite 查詢到字節(jié)碼析蝴,使用 sqlite_bind_int 綁定參數(shù)到 SQLite 語句害捕。
  • 為了防止使用 GCD 創(chuàng)建過多的線程,解決方法是創(chuàng)建串行隊列, 或者使用帶有最大并發(fā)數(shù)限制的 NSOperationQueue闷畸。
  • 線程安全:UIKit只能在主線程執(zhí)行尝盼,除了 UIGraphics、UIBezierPath 之外佑菩,UIImage盾沫、CG、CA殿漠、Foundation 都不能從兩個線程同時訪問赴精。
  • 不要在主線程執(zhí)行磁盤、網(wǎng)絡绞幌、Lock 或者 dispatch_sync蕾哟、發(fā)送消息給其他線程等操作。

更多:iOS面試題合集

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啊奄,一起剝皮案震驚了整個濱河市渐苏,隨后出現(xiàn)的幾起案子掀潮,更是在濱河造成了極大的恐慌菇夸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仪吧,死亡現(xiàn)場離奇詭異庄新,居然都是意外死亡,警方通過查閱死者的電腦和手機薯鼠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門择诈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人出皇,你說我怎么就攤上這事羞芍。” “怎么了郊艘?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵荷科,是天一觀的道長。 經(jīng)常有香客問我纱注,道長畏浆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任狞贱,我火速辦了婚禮刻获,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞎嬉。我一直安慰自己蝎毡,他們只是感情好厚柳,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沐兵,像睡著了一般草娜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痒筒,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天宰闰,我揣著相機與錄音,去河邊找鬼簿透。 笑死移袍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的老充。 我是一名探鬼主播葡盗,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼啡浊!你這毒婦竟也來了觅够?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤巷嚣,失蹤者是張志新(化名)和其女友劉穎喘先,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廷粒,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡窘拯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坝茎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涤姊。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嗤放,靈堂內(nèi)的尸體忽然破棺而出思喊,到底是詐尸還是另有隱情,我是刑警寧澤次酌,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布恨课,位于F島的核電站,受9級特大地震影響和措,放射性物質(zhì)發(fā)生泄漏庄呈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一派阱、第九天 我趴在偏房一處隱蔽的房頂上張望诬留。 院中可真熱鬧,春花似錦、人聲如沸文兑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绿贞。三九已至因块,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間籍铁,已是汗流浹背涡上。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拒名,地道東北人吩愧。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像增显,于是被迫代替她去往敵國和親雁佳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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