iOS項目啟動時間優(yōu)化

背景

項目功能越來越復(fù)雜化,影響到啟動時間(主要是感覺自己菜,需要學(xué)習(xí))

APP啟動時都干了什么事

一般情況下,App 的啟動分為冷啟動和熱啟動,我們這里說的是優(yōu)化冷啟動時間。

冷啟動是指, App 點擊啟動前,它的進程不在系統(tǒng)里,需要系統(tǒng)新創(chuàng)建一個進程分配給它啟動的情況。這是一次完整的啟動過程。

熱啟動是指 ,App 在冷啟動后用戶將 App 退后臺,在 App 的進程還在系統(tǒng)里的情況下,用戶重新啟動進入 App 的過程,這個過程做的事情非常少。

一般而言,App 的啟動時間,指的是從用戶點擊 App 開始,到用戶看到第一個界面之間的時間。總結(jié)來說,App 的啟動主要包括三個階段:
main() 函數(shù)執(zhí)行前;
main() 函數(shù)執(zhí)行后;
首屏渲染完成后。

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

main() 函數(shù)執(zhí)行前的階段啟動時間通過添加環(huán)境變量,可以打印出APP的啟動時間分析:

(Edit scheme -->Run -->Arguments -->Environment Variables)
添加一項:Name為DYLD_PRINT_STATISTICS,Value為1,然后運行項目查看日志

Total pre-main time: 348.75 milliseconds (100.0%)
         dylib loading time: 302.06 milliseconds (86.6%)
        rebase/binding time: 126687488.9 seconds (135339814.9%)
            ObjC setup time:  17.65 milliseconds (5.0%)
           initializer time:  50.90 milliseconds (14.5%)
           slowest intializers :
             libSystem.B.dylib :   5.88 milliseconds (1.6%)
    libMainThreadChecker.dylib :  32.44 milliseconds (9.3%)

在 main() 函數(shù)執(zhí)行前,系統(tǒng)主要會做下面幾件事情(對照上面代碼看):

  • 加載可執(zhí)行文件(App 的.o 文件的集合);
  • 加載動態(tài)鏈接庫,進行 rebase 指針調(diào)整和 bind 符號綁定;
  • Objc 運行時的初始處理,包括 Objc 相關(guān)類的注冊、category 注冊、selector 唯一性檢查等;
  • 初始化,包括了執(zhí)行 +load() 方法、attribute((constructor)) 修飾的函數(shù)的調(diào)用、創(chuàng)建 C++ 靜態(tài)全局變量。

對應(yīng)的啟動速度優(yōu)化:

  • 減少動態(tài)庫加載。每個庫本身都有依賴關(guān)系,蘋果公司建議使用更少的動態(tài)庫,并且建議在使用動態(tài)庫的數(shù)量較多時,盡量將多個動態(tài)庫進行合并。數(shù)量上,蘋果公司最多可以支持 6 個非系統(tǒng)動態(tài)庫合并為一個。
  • 移除沒有用到的類或者方法。
  • +load() 方法的內(nèi)容可以放到首屏渲染完成后再執(zhí)行,或使用 +initialize() 方法替換掉。因為,在一個 +load() 方法里,進行運行時方法替換操作會帶來 4 毫秒的消耗。
  • 控制 C++ 全局變量的數(shù)量。
2.main() 函數(shù)執(zhí)行后啟動時間

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

首頁的業(yè)務(wù)代碼都是要在這個階段,也就是首屏渲染前執(zhí)行的,主要包括了:

  • 首屏初始化所需配置文件的讀寫操作;
  • 首屏列表大數(shù)據(jù)的讀??;
  • 首屏渲染的大量計算等。

很多時候,開發(fā)者會把各種初始化工作都放到這個階段執(zhí)行,導(dǎo)致渲染完成滯后。更加優(yōu)化的開發(fā)方式,應(yīng)該是從功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 啟動必要的初始化功能,而哪些是只需要在對應(yīng)功能開始使用時才需要初始化的。梳理完之后,將這些初始化功能分別放到合適的階段進行。例如地圖,支付,統(tǒng)計,推送等功能的注冊,有些是必須放在appDelegate中注冊,有些可以放在首屏渲染完成之后注冊,或者有些可以放在子線程注冊。

3.首屏渲染完成后

首屏渲染后的這個階段,主要完成的是,非首屏其他業(yè)務(wù)服務(wù)模塊的初始化、監(jiān)聽的注冊、配置文件的讀取等。從函數(shù)上來看,這個階段指的就是截止到 didFinishLaunchingWithOptions 方法作用域內(nèi)執(zhí)行首屏渲染之后的所有方法執(zhí)行完成。
這個階段就是從渲染完成時開始,到 didFinishLaunchingWithOptions 方法作用域結(jié)束時結(jié)束。這個階段用戶已經(jīng)能夠看到 App 的首頁信息了,所以優(yōu)化的優(yōu)先級排在最后。但是,那些會卡住主線程的方法還是需要最優(yōu)先處理的,不然還是會影響到用戶后面的交互操作。

功能級別的啟動優(yōu)化

功能級別的啟動優(yōu)化,就是要從 main() 函數(shù)執(zhí)行后這個階段下手。
優(yōu)化的思路是:main() 函數(shù)開始執(zhí)行后到首屏渲染完成前只處理首屏相關(guān)的業(yè)務(wù),其他非首屏業(yè)務(wù)的初始化、監(jiān)聽注冊、配置文件讀取等都放到首屏渲染完成后去做。

其他小優(yōu)化

  • 合并功能相似的類和擴展(Category)
    由于Category的實現(xiàn)原理,和ObjC的動態(tài)綁定有很強的關(guān)系,實際上類的擴展是比較占用啟動時間的。盡量可能合并一些擴展,并不是讓你不使用擴展
  • 清理無用的資源圖片,壓縮較大的資源圖片
    推薦使用LSUnusedResources查找無用的資源圖片,使用ImageOptim壓縮較大的資源圖片
  • 優(yōu)化applicationWillFinishLaunching
    需要在applicationWillFinishLaunching里處理的業(yè)務(wù)較多時,可以管理起這些任務(wù)
    將不需要馬上在applicationWillFinishLaunching執(zhí)行的代碼延后執(zhí)行
  • 優(yōu)化rootViewController
    rootViewController的加載,適當(dāng)將某一級的childViewController或subviews延后加載
    如果你的App可能會被后臺拉起并冷啟動,可考慮不加載rootViewController
  • 不使用xib,直接使用代碼加載首頁視圖
  • NSUserDefaults實際上是在Library文件夾下會生產(chǎn)一個plist文件,如果文件太大的話一次能讀取到內(nèi)存中可能很耗時,如果耗時很大的話需要拆分(需考慮老版本覆蓋安裝兼容問題)
  • 每次用NSLog方式打印會隱式的創(chuàng)建一個Calendar,因此需要刪減啟動時各業(yè)務(wù)方打的log,或者僅僅針對內(nèi)測版輸出log
  • 梳理應(yīng)用啟動時發(fā)送的所有網(wǎng)絡(luò)請求,是否可以統(tǒng)一在異步線程請求

方法級別啟動優(yōu)化

檢查首屏渲染完成前主線程上的耗時方法,將非剛需的耗時方法滯后或異步執(zhí)行。耗時較長的方法主要發(fā)生在計算大量數(shù)據(jù)的情況下,例如加載、編輯、存儲圖片和文件等資源。
比如+load() 方法,一個耗時 4 毫秒,100 個就是 400 毫秒。ReactiveCocoa 框架每創(chuàng)建一個信號都有 6 毫秒的耗時。這樣,稍不注意各種信號的創(chuàng)建就都被放在了首屏渲染完成前,進而導(dǎo)致 App 的啟動速度大幅變慢等。

目前來看,對 App 啟動速度的監(jiān)控,主要有兩種手段。

第一種方法是,定時抓取主線程上的方法調(diào)用堆棧,計算一段時間里各個方法的耗時。

Xcode 工具套件里自帶的 Time Profiler ,采用的就是這種方式。開發(fā)類似工具成本不高,能夠快速開發(fā)后集成到你的 App 中,以便在真實環(huán)境中進行檢查。

對于定時時間間隔的控制

  • 間隔長:會漏掉某些方法,從而導(dǎo)致檢查出來的耗時不精確
  • 間隔短:抓取堆棧這個方法本身調(diào)用過多會影響整體耗時,導(dǎo)致結(jié)果不準(zhǔn)確
  • 定時抓取主線程調(diào)用棧的方式精準(zhǔn)度不夠高,做參考足以
  • 大神得出最合適時間為 0.01 秒,雖然導(dǎo)致許多運行速度快的方法監(jiān)控誤差,對整體耗時影響小

第二種方法是,對 objc_msgSend 方法進行 hook 來掌握所有方法的執(zhí)行耗時。

Hook:在原方法開始執(zhí)行時換成執(zhí)行其他指定的方法,或者在原有方法執(zhí)行前后執(zhí)行你指定的方法,來達到掌握和改變指定方法的目的。
hook objc_msgSend 這種方式的優(yōu)點是非常精確,而缺點是只能針對 Objective-C 的方法。對c方法和block需要借助第三方框架處理,編寫維護成本高。

檢查方法耗時的工具

推薦使用工具SMCallTrace
友情提示:需要在SMCallTrace.m中打開第54行的注釋。

自主實現(xiàn)時間檢測工具

實現(xiàn)VC耗時檢測

學(xué)習(xí)筆記,參考多位大神,感謝大神分享!

未完待續(xù)...

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

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

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