應(yīng)用啟動環(huán)節(jié)熙参,我們大致分為2種啟動:即冷啟動(Cold Launch)和熱啟動(Warm Launch)场靴,針對優(yōu)化队丝,我們主要針對冷啟動玩祟。
知識點:打印啟動時間
通過添加環(huán)境變量可以打印出APP的啟動時間分析(Edit scheme -> Run -> Arguments)
DYLD_PRINT_STATISTICS設(shè)置為1
如果需要更詳細(xì)的信息腹缩,那就將DYLD_PRINT_STATISTICS_DETAILS設(shè)置為1
一般而言,大家把iOS冷啟動的過程定義為:從用戶點擊App圖標(biāo)開始到appDelegate didFinishLaunching方法執(zhí)行完成為止。這個過程主要分為兩個階段:
main()函數(shù)之前藏鹊,即操作系統(tǒng)加載App可執(zhí)行文件到內(nèi)存润讥,然后執(zhí)行一系列的加載&鏈接等工作,最后執(zhí)行至App的main()函數(shù)盘寡。
main()函數(shù)之后楚殿,即從main()開始,到appDelegate的didFinishLaunchingWithOptions方法執(zhí)行完畢竿痰。
然而脆粥,當(dāng)didFinishLaunchingWithOptions執(zhí)行完成時,用戶還沒有看到App的主界面影涉,也不能開始使用App变隔。例如在外賣App中,App還需要做一些初始化工作蟹倾,然后經(jīng)歷定位匣缘、首頁請求、首頁渲染等過程后鲜棠,用戶才能真正看到數(shù)據(jù)內(nèi)容并開始使用肌厨,我們認(rèn)為這個時候冷啟動才算完成。
在調(diào)用main()函數(shù)之前豁陆,基本所有的工作都是由操作系統(tǒng)完成的柑爸,開發(fā)者能夠插手的地方不多,所以如果想要優(yōu)化這段時間献联,就必須先了解一下竖配,操作系統(tǒng)在main()之前做了什么。main()之前操作系統(tǒng)所做的工作就是把可執(zhí)行文件(Mach-O格式)加載到內(nèi)存空間里逆,然后加載動態(tài)鏈接庫dyld进胯,再執(zhí)行一系列動態(tài)鏈接操作和初始化操作的過程(加載、綁定原押、及初始化方法)胁镐。這方面的資料網(wǎng)上比較多,但重復(fù)性較高诸衔,此處附上一篇WWDC的Topic:Optimizing App Startup Time盯漂。
dyld
'dyld(dynamic link editor),Apple的動態(tài)鏈接器笨农,可以用來裝載Mach-O文件(可執(zhí)行文件就缆、動態(tài)庫等)
啟動APP時,dyld所做的事情有:
裝載APP的可執(zhí)行文件谒亦,同時會遞歸加載所有依賴的動態(tài)庫
當(dāng)dyld把可執(zhí)行文件竭宰、動態(tài)庫都裝載完畢后空郊,會通知Runtime進(jìn)行下一步的處理
具體關(guān)于dyld的講解推薦dyld專題文章講的很細(xì)致。
Runtime 運(yùn)行時
啟動APP時切揭,runtime所做的事情有
調(diào)用map_images進(jìn)行可執(zhí)行文件內(nèi)容的解析和處理
在load_images中調(diào)用call_load_methods狞甚,調(diào)用所有Class和Category的+load方法
進(jìn)行各種objc結(jié)構(gòu)的初始化(注冊O(shè)bjc類 、初始化類對象等等)
調(diào)用C++靜態(tài)初始化器和attribute((constructor))修飾的函數(shù)
到此為止廓旬,可執(zhí)行文件和動態(tài)庫中所有的符號(Class哼审,Protocol,Selector孕豹,IMP涩盾,…)都已經(jīng)按格式成功加載到內(nèi)存中,被runtime 所管理
了解完main()之前的加載過程后励背,我們可以分析出一些影響mian函數(shù)之前消耗時間的因素:
動態(tài)庫加載越多旁赊,啟動越慢。
ObjC類椅野,方法越多,啟動越慢籍胯。
ObjC的+load越多竟闪,啟動越慢。
C的constructor函數(shù)越多杖狼,啟動越慢炼蛤。
C++靜態(tài)對象越多,啟動越慢蝶涩。
代碼瘦身
隨著業(yè)務(wù)的迭代理朋,不斷有新的代碼加入,同時也會廢棄掉無用的代碼和資源文件绿聘,但是工程中經(jīng)常有無用的代碼和文件被遺棄在角落里嗽上,沒有及時被清理掉。這些無用的部分一方面增大了App的包體積熄攘,另一方便也拖慢了App的冷啟動速度兽愤,所以及時清理掉這些無用的代碼和資源十分有必要。
通過對Mach-O文件的了解挪圾,可以知道__TEXT:__objc_methname:中包含了代碼中的所有方法浅萧,而__DATA__objc_selrefs中則包含了所有被使用的方法的引用,通過取兩個集合的差集就可以得到所有未被使用的代碼哲思。核心方法如下洼畅,具體可以參考:objc_cover:
def referenced_selectors(path):
re_sel = re.compile("__TEXT:__objc_methname:(.+)") //獲取所有方法
refs = set()
lines = os.popen("/usr/bin/otool -v -s __DATA __objc_selrefs %s" % path).readlines() # ios & mac //真正被使用的方法
for line in lines:
results = re_sel.findall(line)
if results:
refs.add(results[0])
return refs
+load優(yōu)化
目前iOS App中或多或少的都會寫一些+load方法,用于在App啟動執(zhí)行一些操作棚赔,+load方法在Initializers階段被執(zhí)行帝簇,但過多+load方法則會拖慢啟動速度徘郭,對于大中型的App更是如此。通過對App中+load的方法分析己儒,發(fā)現(xiàn)很多代碼雖然需要在App啟動時較早的時機(jī)進(jìn)行初始化崎岂,但并不需要在+load這樣非常靠前的位置闪湾,完全是可以延遲到App冷啟動后的某個時間節(jié)點冲甘,例如一些路由操作。
優(yōu)化耗時操作
在main()之后主要工作是各種啟動項的執(zhí)行途样,主界面的構(gòu)建江醇,例如TabBarVC,HomeVC等等何暇。資源的加載陶夜,如圖片I/O、圖片解碼裆站、archive文檔等条辟。這些操作中可能會隱含著一些耗時操作,靠單純閱讀非常難以發(fā)現(xiàn)宏胯,如何發(fā)現(xiàn)這些耗時點呢羽嫡?找到合適的工具就會事半功倍。
Time Profiler
Time Profiler是Xcode自帶的時間性能分析工具肩袍,它按照固定的時間間隔來跟蹤每一個線程的堆棧信息杭棵,通過統(tǒng)計比較時間間隔之間的堆棧狀態(tài),來推算某個方法執(zhí)行了多久氛赐,并獲得一個近似值魂爪。Time Profiler的使用方法網(wǎng)上有很多使用教程,這里我們也不過多介紹艰管,附上一篇使用文檔:Instruments Tutorial with Swift: Getting Started滓侍。
火焰圖
除了Time Profiler,火焰圖也是一個分析CPU耗時的利器蛙婴,相比于Time Profiler粗井,火焰圖更加清晰〗滞迹火焰圖分析的產(chǎn)物是一張調(diào)用棧耗時圖片浇衬,之所以稱為火焰圖,是因為整個圖形看起來就像一團(tuán)跳動的火焰餐济,火焰尖部是調(diào)用棧的棧頂耘擂,底部是棧底,縱向表示調(diào)用棧的深度絮姆,橫向表示消耗的時間醉冤。一個格子的寬度越大秩霍,越說明其可能是瓶頸。分析火焰圖主要就是看那些比較寬大的火苗蚁阳,特別留意那些類似“平頂山”的火苗铃绒。
列舉幾點具體優(yōu)化的方向
閃屏業(yè)務(wù)上優(yōu)化點
現(xiàn)在許多App在啟動時并不直接進(jìn)入首頁,而是會向用戶展示一個持續(xù)一小段時間的閃屏頁螺捐,如果使用恰當(dāng)颠悬,這個閃屏頁就能幫我們節(jié)省一些啟動時間。因為當(dāng)一個App比較復(fù)雜的時候定血,啟動時首次構(gòu)建App的UI就是一個比較耗時的過程赔癌,假定這個時間是0.2秒,如果我們是先構(gòu)建首頁UI澜沟,然后再在Window上加上這個閃屏頁灾票,那么冷啟動時,App就會實實在在地卡住0.2秒茫虽,但是如果我們是先把閃屏頁作為App的RootViewController刊苍,那么這個構(gòu)建過程就會很快。因為閃屏頁只有一個簡單的ImageView濒析,而這個ImageView則會向用戶展示一小段時間班缰,這時我們就可以利用這一段時間來構(gòu)建首頁UI了,一舉兩得悼枢。
TOGO途歌共享車客戶端就是使用的這種方案。
對于快速迭代的App脾拆,隨著業(yè)務(wù)復(fù)雜度的增加馒索,冷啟動時長會不可避免的增加。冷啟動流程也是一個比較復(fù)雜的過程名船,當(dāng)遇到冷啟動性能瓶頸時绰上,我們可以根據(jù)App自身的特點,配合工具的使用渠驼,從多方面蜈块、多角度進(jìn)行優(yōu)化。同時迷扇,優(yōu)化冷啟動存量問題只是冷啟動治理的第一步百揭,因為冷啟動性能問題并不是一日造成的,也不能簡單的通過一次優(yōu)化工作就能解決蜓席,我們需要通過合理的設(shè)計器一、規(guī)范的約束,來有效地管控性能問題的增量厨内,并通過持續(xù)的線上監(jiān)控來及時發(fā)現(xiàn)并修正性能問題祈秕,這樣才能夠長期保證良好的App冷啟動體驗渺贤。