iOS - 優(yōu)化App冷啟動(dòng)速度

1. App的啟動(dòng)分為三個(gè)主要階段:

  • main()函數(shù)執(zhí)行前

  • main()函數(shù)執(zhí)行后(從main函數(shù)執(zhí)行,到設(shè)置self.window.rootViewController)

  • 首屏渲染完成后(從設(shè)置self.window.rootViewController到didFinishLaunchWithOptions方法作用域結(jié)束)

main函數(shù)執(zhí)行前,系統(tǒng)會(huì)做的事情:
  • 加載可執(zhí)行文件(App的.o文件集合)

  • 加載動(dòng)態(tài)鏈接庫(kù),進(jìn)行rebase指針調(diào)整和bind符號(hào)綁定

  • Objc運(yùn)行時(shí)的初始處理,包括Objc相關(guān)類(lèi)注冊(cè)、category注冊(cè)、selector唯一性檢查等

  • 初始化,包括了執(zhí)行+load()方法、attribute((constructor))修飾的函數(shù)的調(diào)用、創(chuàng)建C++靜態(tài)全局變量。

main()函數(shù)執(zhí)行后:

main()函數(shù)執(zhí)行后的階段,指的是從main()函數(shù)執(zhí)行開(kāi)始,到appDelegate的didFinishLaunchingWithOpentions方法里首屏渲染相關(guān)方法執(zhí)行完成。

這里應(yīng)該是從功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是App啟動(dòng)必要的初始化功能,哪些是只需要在對(duì)應(yīng)功能開(kāi)始使用時(shí)才需要初始化的,將這些放到各自合適的階段執(zhí)行。

首屏渲染完成后:

首屏渲染后的這個(gè)階段,指的是didFinishLaunchWithOptions方法作用域內(nèi)執(zhí)行首屏渲染之后的所有方法執(zhí)行完成,即從 設(shè)置了self.window.rootViewController開(kāi)始 到 didFinishLaunchWithOptions方法作用域 結(jié)束。

首屏渲染完成后用戶(hù)就可以看到App的首頁(yè)信息了,把這個(gè)階段內(nèi)卡住主線程的方法解決掉就可以了。

注解:
  • App啟動(dòng)后,首先加載可執(zhí)行文件,然后加載dyld,然后加載所有依賴(lài)庫(kù),然后調(diào)用所有的+load(),然后調(diào)用main(),然后調(diào)用UIApplicationMain(),然后調(diào)用AppDelegate的代理didFinishLaunchWithOptions.

  • 可執(zhí)行文件是指Mach-O格式的文件,也就是App中所有.o文件的集合體,從這里可以獲取dyld的路徑,然后加載dyld。

  • dyld是指蘋(píng)果的動(dòng)態(tài)鏈接器,加載dyld后,就會(huì)去初始化運(yùn)行環(huán)境,開(kāi)啟緩存策略,加載依賴(lài)庫(kù),并且會(huì)調(diào)用每一個(gè)依賴(lài)庫(kù)的初始化方法,包括RunTime也是在這里被初始化的,當(dāng)所有的依賴(lài)庫(kù)都被初始化完成后,RunTime會(huì)對(duì)項(xiàng)目中所有的類(lèi)進(jìn)行類(lèi)初始化,調(diào)用所有的+load()方法,最后dyld會(huì)返回main函數(shù)地址,然后main函數(shù)會(huì)被調(diào)用。

  • 知曉上述的流程后,我們就明白為什么優(yōu)化啟動(dòng)速度,要去減少動(dòng)態(tài)庫(kù)加載,要少用+load(),理論明白了之后,我們就要看看具體怎么做了。

  • 動(dòng)態(tài)庫(kù)是指可以共享的代碼文件、資源文件、頭文件等的打包集合體。在Xcode->Targets->General->Link Binary With Libraries可以檢查自己的庫(kù),

  • 減少+load()的使用,將里面的內(nèi)容放到渲染結(jié)束后去做,或者用+initialize()代替。+load()方法在main()調(diào)用前就會(huì)調(diào)用,而+initialize()方法是在類(lèi)第一次收到消息后,才會(huì)調(diào)用,兩者的區(qū)別可以參考這里

    Main函數(shù)調(diào)用前

    Main函數(shù)調(diào)用后

2.具體優(yōu)化方法

(1)減少+load()的使用

使用+initialize()的方法代替+load(),注意把邏輯移動(dòng)到+initialize()時(shí),要注意避免+initialize()的重復(fù)調(diào)用問(wèn)題,可以使用dispatch_once()讓邏輯只執(zhí)行一次。

(2)對(duì)多個(gè)動(dòng)態(tài)庫(kù)進(jìn)行合并

蘋(píng)果公司建議使用更少的動(dòng)態(tài)庫(kù),并且建議在使用動(dòng)態(tài)庫(kù)的數(shù)量較多時(shí),盡量將多個(gè)動(dòng)態(tài)庫(kù)進(jìn)行合并。數(shù)量上,蘋(píng)果公司最多可以支持6個(gè)非系統(tǒng)動(dòng)態(tài)庫(kù)合并為一個(gè)。

(3)優(yōu)化類(lèi)、方法、全局變量

減少加載啟動(dòng)后不會(huì)去使用的類(lèi)或方法;控制C++全局變量的數(shù)量

(4)功能級(jí)別的啟動(dòng)優(yōu)化

main()開(kāi)始執(zhí)行后到首屏渲染完成前,只處理首屏相關(guān)的業(yè)務(wù),其他的都放到首屏渲染完成后去做。

(5)方法級(jí)別的啟動(dòng)優(yōu)化

首先檢查首屏渲染完成前主線程上的耗時(shí)操作,將沒(méi)必要的操作滯后或異步。通常耗時(shí)操作有:加載、編輯、存儲(chǔ)圖片和文件等資源。

3. 查看耗時(shí)

(1)查看Main()調(diào)用前花費(fèi)的總時(shí)間

在Product->Scheme->Edit Scheme->Run->Arguments->Environment Variables->DYLD_PRINT_STATISTICS設(shè)置為YES,就可以在控制臺(tái)中查看main函數(shù)執(zhí)行前總共花費(fèi)的多長(zhǎng)時(shí)間。

設(shè)置環(huán)境變量.png

控制臺(tái)會(huì)輸出pre-main的總時(shí)間.png

(2)查看加載了多少動(dòng)態(tài)庫(kù)

在Product->Scheme->Edit Scheme->Run->Diagnostics->Logging->勾選Dynamic Library Loads,就可以在控制臺(tái)中查看本項(xiàng)目中加載的所有動(dòng)態(tài)庫(kù)(包括系統(tǒng)的和自己的)。


image.png
(3)查看Main函數(shù)啟動(dòng)后的耗時(shí)

main函數(shù)調(diào)用后的耗時(shí),可以使用一些工具來(lái)監(jiān)控,有一種非常笨但是很實(shí)用的方法,就是通過(guò)打點(diǎn),在didFinishLaunchingWithOptions開(kāi)始前打一個(gè)點(diǎn),在App顯示完成第一個(gè)界面再打一個(gè)點(diǎn),計(jì)算兩個(gè)點(diǎn)之間的耗時(shí),就可以知道m(xù)ain函數(shù)調(diào)用后到界面顯示出來(lái)的耗時(shí)了,但是這樣只能籠統(tǒng)的知道總的耗時(shí),并不能準(zhǔn)確的知道時(shí)間花在了哪里。

如果想用這個(gè)打點(diǎn)法的話,推薦一個(gè)打點(diǎn)工具BLStopwatch

如果想準(zhǔn)確知道時(shí)間都花在了哪里,推薦使用下面兩種方法。

4. 監(jiān)控App啟動(dòng)耗時(shí),精準(zhǔn)找出時(shí)間都花在了哪里,方便逐一優(yōu)化

準(zhǔn)確監(jiān)控方法有兩種:
  1. 定時(shí)抓取主線程上的方法調(diào)用堆棧,計(jì)算一段時(shí)間里各個(gè)方法的耗時(shí)。Xcode自帶的Time Profiler就是用的這種方法。

  2. 對(duì)objc_msgSend方法進(jìn)行hook來(lái)掌握所有方法的執(zhí)行耗時(shí)。

根據(jù)這兩種方法,分別實(shí)現(xiàn)兩個(gè)工具,來(lái)監(jiān)控耗時(shí)

由于能力有限,我只根據(jù)第一種方法做出來(lái)一個(gè)計(jì)算某個(gè)線程的耗時(shí)工具,放在了這里BSMonitorTimeTool,大致思路如下:

(1). 通過(guò)定時(shí)器,每隔0.01s,獲取一次主線程的函數(shù)堆棧,將函數(shù)名稱(chēng)、函數(shù)地址、函數(shù)耗時(shí)模型化為TimeModel,保存在callStackDict中,其中key為函數(shù)地址,value為T(mén)imeModel

(2). 定時(shí)執(zhí)行的回調(diào)中,每次都判斷函數(shù)地址是否存在,如果已經(jīng)存在此函數(shù)地址,就講對(duì)應(yīng)的TimeModel中的耗時(shí)增加0.01s;如果不存在此函數(shù)地址,就初始化一個(gè)TimeModel,并將時(shí)間設(shè)置為0.01s。

(3). 當(dāng)主界面顯示完成之后,輸出此callStackDict,即可查看主線程中每個(gè)方法的耗時(shí)

5. 歡迎大家指正錯(cuò)誤,希望能夠共同進(jìn)步

本文章是參考了很多大佬的文章,歡迎各位前去膜拜

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 一個(gè)項(xiàng)目做的時(shí)間長(zhǎng)了,啟動(dòng)流程往往容易雜亂,庫(kù)也用的越來(lái)越多,APP的啟動(dòng)時(shí)間也會(huì)慢慢變長(zhǎng)。本次將針對(duì)iOS...
    醬油瓶2閱讀 3,653評(píng)論 0 12
  • 應(yīng)用啟動(dòng)時(shí)間,直接影響用戶(hù)對(duì)一款應(yīng)用的判斷和使用體驗(yàn)。頭條主app本身就包含非常多并且復(fù)雜度高的業(yè)務(wù)模塊(如新聞、...
    新_1740閱讀 477評(píng)論 0 2
  • 前言:本文簡(jiǎn)單描述APP啟動(dòng)過(guò)程和監(jiān)控,一些深入原理性的東西可能需要繞路了,站在大神的肩膀上,簡(jiǎn)單總結(jié)跟APP啟動(dòng)...
    夢(mèng)蕊dream閱讀 3,099評(píng)論 1 14
  • 聽(tīng),它聲音的靜謐 如耳畔,滑落的沙礫 疏疏稀稀, 伴奏著歸音 看,它目光的晶瑩 似水中,淌光的漣漪 在此時(shí)的回憶,...
    污流閱讀 531評(píng)論 2 6
  • 開(kāi)學(xué)一個(gè)月了,這個(gè)月,我的感覺(jué)是,懵,懵懂,迷茫,又憧憬。 怎么說(shuō)呢?懵,是不知所措,不懂怎么去做到更好,傻傻的以...
    瀟湘似夢(mèng)閱讀 269評(píng)論 0 0

友情鏈接更多精彩內(nèi)容