一、應(yīng)用啟動(dòng)流程
iOS應(yīng)用的啟動(dòng)可分為pre-main階段和main()階段,其中系統(tǒng)做的事情依次是:
1. pre-main階段
1.1. 加載應(yīng)用的可執(zhí)行文件
1.2. 加載動(dòng)態(tài)鏈接庫(kù)加載器dyld(dynamic loader)
1.3. dyld遞歸加載應(yīng)用所有依賴的dylib(dynamic library 動(dòng)態(tài)鏈接庫(kù))
2. main()階段
2.1. dyld調(diào)用main()
2.2. 調(diào)用UIApplicationMain()
2.3. 調(diào)用applicationWillFinishLaunching
2.4. 調(diào)用didFinishLaunchingWithOptions
二、應(yīng)用啟動(dòng)優(yōu)化
1.pre-main階段的優(yōu)化
要對(duì)pre-main階段的耗時(shí)做優(yōu)化,需要根據(jù)dyld加載的過(guò)程分為4步:
1.1. Load dylibs
這一階段dyld會(huì)分析應(yīng)用依賴的dylib,找到其mach-o文件,打開(kāi)和讀取這些文件并驗(yàn)證其有效性,接著會(huì)找到代碼簽名注冊(cè)到內(nèi)核,最后對(duì)dylib的每一個(gè)segment調(diào)用mmap()。
一般情況下,iOS應(yīng)用會(huì)加載100-400個(gè)dylibs,其中大部分是系統(tǒng)庫(kù),這部分dylib的加載系統(tǒng)已經(jīng)做了優(yōu)化。
所以,依賴的dylib越少越好。在這一步,我們可以做的優(yōu)化有:
- 盡量不使用內(nèi)嵌(embedded)的dylib,加載內(nèi)嵌dylib性能開(kāi)銷較大
- 合并已有的dylib和使用靜態(tài)庫(kù)(static archives),減少dylib的使用個(gè)數(shù)
- 懶加載dylib,但是要注意dlopen()可能造成一些問(wèn)題,且實(shí)際上懶加載做的工作會(huì)更多
1.2. Rebase/Bind
在dylib的加載過(guò)程中,系統(tǒng)為了安全考慮,引入了ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名。由于ASLR的存在,鏡像(Image,包括可執(zhí)行文件、dylib和bundle)會(huì)在隨機(jī)的地址上加載,和之前指針指向的地址(preferred_address)會(huì)有一個(gè)偏差(slide),dyld需要修正這個(gè)偏差,來(lái)指向正確的地址。Rebase在前,Bind在后,Rebase做的是將鏡像讀入內(nèi)存,修正鏡像內(nèi)部的指針,性能消耗主要在IO。Bind做的是查詢符號(hào)表,設(shè)置指向鏡像外部的指針,性能消耗主要在CPU計(jì)算。
所以,指針數(shù)量越少越好。在這一步,我們可以做的優(yōu)化有:
- 減少ObjC類(class)、方法(selector)、分類(category)的數(shù)量
- 減少C++虛函數(shù)的的數(shù)量(創(chuàng)建虛函數(shù)表有開(kāi)銷)
- 使用Swift structs(內(nèi)部做了優(yōu)化,符號(hào)數(shù)量更少)
1.3. Objc setup
大部分ObjC初始化工作已經(jīng)在Rebase/Bind階段做完了,這一步dyld會(huì)注冊(cè)所有聲明過(guò)的ObjC類,將分類插入到類的方法列表里,再檢查每個(gè)selector的唯一性。
在這一步倒沒(méi)什么優(yōu)化可做的,Rebase/Bind階段優(yōu)化好了,這一步的耗時(shí)也會(huì)減少。
1.4. Initializers
到了這一階段,dyld開(kāi)始運(yùn)行程序的初始化函數(shù),調(diào)用每個(gè)Objc類和分類的+load方法,調(diào)用C/C++ 中的構(gòu)造器函數(shù)(用attribute((constructor))修飾的函數(shù)),和創(chuàng)建非基本類型的C++靜態(tài)全局變量。Initializers階段執(zhí)行完后,dyld開(kāi)始調(diào)用main()函數(shù)。
在這一步,我們可以做的優(yōu)化有:
- 少在類的+load方法里做事情,盡量把這些事情推遲到+initiailize
- 減少構(gòu)造器函數(shù)個(gè)數(shù),在構(gòu)造器函數(shù)里少做些事情
- 減少C++靜態(tài)全局變量的個(gè)數(shù)
2.main()階段的優(yōu)化
這一階段的優(yōu)化主要是減少didFinishLaunchingWithOptions方法里的工作,在didFinishLaunchingWithOptions方法里,我們會(huì)創(chuàng)建應(yīng)用的window,指定其rootViewController,調(diào)用window的makeKeyAndVisible方法讓其可見(jiàn)。
由于業(yè)務(wù)需要,我們會(huì)初始化各個(gè)三方庫(kù),設(shè)置系統(tǒng)UI風(fēng)格,檢查是否需要顯示引導(dǎo)頁(yè)、是否需要登錄、是否有新版本等,由于歷史原因,這里的代碼容易變得比較龐大,啟動(dòng)耗時(shí)難以控制。
所以,滿足業(yè)務(wù)需要的前提下,didFinishLaunchingWithOptions在主線程里做的事情越少越好。在這一步,我們可以做的優(yōu)化有:
- 梳理各個(gè)三方庫(kù),找到可以延遲加載的庫(kù),做延遲加載處理,比如放到首頁(yè)控制器的viewDidAppear方法里。
- 梳理業(yè)務(wù)邏輯,把可以延遲執(zhí)行的邏輯,做延遲執(zhí)行處理。比如檢查新版本、注冊(cè)推送通知等邏輯。
- 避免復(fù)雜/多余的計(jì)算。
- 避免在首頁(yè)控制器的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完,首頁(yè)控制器才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理。
- 采用性能更好的API。
- 首頁(yè)控制器用純代碼方式來(lái)構(gòu)建。