一、啟動(dòng)時(shí)間測量(優(yōu)化啟動(dòng)時(shí)間)
1梢褐、main函數(shù)前執(zhí)行的時(shí)間
在此階段系統(tǒng)做的任務(wù)為:
1.1. 加載應(yīng)用的可執(zhí)行文件
1.2. 加載動(dòng)態(tài)鏈接庫加載器dyld(dynamic loader)
1.3. dyld遞歸加載應(yīng)用所有依賴的dylib(dynamic library 動(dòng)態(tài)鏈接庫)
測試方法:在 Xcode 中 Edit scheme -> Run -> Auguments 將環(huán)境變量DYLD_PRINT_STATISTICS 設(shè)為1 辛块。然后就可以看到控制臺(tái)打印出main之前的時(shí)間
打印結(jié)果如下:(單位:毫秒)
Total pre-main time: 675.00 milliseconds (100.0%)
dylib loading time:? 42.79 milliseconds (6.3%)
rebase/binding time:? 39.88 milliseconds (5.9%)
ObjC setup time: 161.19 milliseconds (23.8%)
initializer time: 431.02 milliseconds (63.8%)
slowest intializers :
libSystem.B.dylib :? 10.13 milliseconds (1.5%)
libMainThreadChecker.dylib :? 21.36 milliseconds (3.1%)
libglInterpose.dylib :? 86.58 milliseconds (12.8%)
libMTLInterpose.dylib :? 21.31 milliseconds (3.1%)
ModelIO :? 17.24 milliseconds (2.5%)
?XXX : 371.80 milliseconds (55.0%)
Load dylibs
這一階段dyld會(huì)分析應(yīng)用依賴的dylib围俘,找到其mach-o文件蛇损,打開和讀取這些文件并驗(yàn)證其有效性豹绪,接著會(huì)找到代碼簽名注冊到內(nèi)核,最后對dylib的每一個(gè)segment調(diào)用mmap()镇防。
一般情況下纽门,iOS應(yīng)用會(huì)加載100-400個(gè)dylibs,其中大部分是系統(tǒng)庫营罢,這部分dylib的加載系統(tǒng)已經(jīng)做了優(yōu)化。
所以饼齿,依賴的dylib越少越好饲漾。在這一步,我們可以做的優(yōu)化有:
盡量不使用內(nèi)嵌(embedded)的dylib缕溉,加載內(nèi)嵌dylib性能開銷較大
合并已有的dylib和使用靜態(tài)庫(static archives)考传,減少dylib的使用個(gè)數(shù)
懶加載dylib,但是要注意dlopen()可能造成一些問題证鸥,且實(shí)際上懶加載做的工作會(huì)更多
?Rebase/Bind
在dylib的加載過程中僚楞,系統(tǒng)為了安全考慮,引入了ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名枉层。由于ASLR的存在泉褐,鏡像(Image,包括可執(zhí)行文件鸟蜡、dylib和bundle)會(huì)在隨機(jī)的地址上加載膜赃,和之前指針指向的地址(preferred_address)會(huì)有一個(gè)偏差(slide),dyld需要修正這個(gè)偏差揉忘,來指向正確的地址跳座。
Rebase在前端铛,Bind在后,Rebase做的是將鏡像讀入內(nèi)存疲眷,修正鏡像內(nèi)部的指針禾蚕,性能消耗主要在IO。Bind做的是查詢符號表狂丝,設(shè)置指向鏡像外部的指針换淆,性能消耗主要在CPU計(jì)算。所以美侦,指針數(shù)量越少越好产舞。
在這一步,我們可以做的優(yōu)化有:
減少ObjC類(class)菠剩、方法(selector)易猫、分類(category)的數(shù)量
減少C++虛函數(shù)的的數(shù)量(創(chuàng)建虛函數(shù)表有開銷)
使用Swift structs(內(nèi)部做了優(yōu)化,符號數(shù)量更少)
Objc setup
大部分ObjC初始化工作已經(jīng)在Rebase/Bind階段做完了具壮,這一步dyld會(huì)注冊所有聲明過的ObjC類准颓,將分類插入到類的方法列表里,再檢查每個(gè)selector的唯一性棺妓。
在這一步倒沒什么優(yōu)化可做的攘已,Rebase/Bind階段優(yōu)化好了,這一步的耗時(shí)也會(huì)減少怜跑。
?Initializers
到了這一階段样勃,dyld開始運(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開始調(diào)用main()函數(shù)辫樱。
在這一步,我們可以做的優(yōu)化有:?
1.少在類的+load方法里做事情俊庇,盡量把這些事情推遲到+initiailize
2.減少構(gòu)造器函數(shù)個(gè)數(shù)狮暑,在構(gòu)造器函數(shù)里少做些事情
3.減少C++靜態(tài)全局變量的個(gè)數(shù)
2、main函數(shù)后到didlaunchoption或者到RootVC的Viewdidload方法
2.1. dyld調(diào)用main()?
2.2. 調(diào)用UIApplicationMain()?
2.3. 調(diào)用applicationWillFinishLaunching
2.4. 調(diào)用didFinishLaunchingWithOptions
測試方法:
在main函數(shù)中聲明 ?CFAbsoluteTime startTime; ?startTime = CFAbsoluteTimeGetCurrent();//記錄進(jìn)入main函數(shù)時(shí)的時(shí)間
在RootViewController中聲明extern??CFAbsoluteTime startTime;在viewdidload中計(jì)算時(shí)差:CFAbsoluteTimeGetCurrent() - startTime
這一階段的優(yōu)化主要是減少didFinishLaunchingWithOptions方法里的工作辉饱,在didFinishLaunchingWithOptions方法里搬男,我們會(huì)創(chuàng)建應(yīng)用的window,指定其rootViewController彭沼,調(diào)用window的makeKeyAndVisible方法讓其可見止后。由于業(yè)務(wù)需要,我們會(huì)初始化各個(gè)二方/三方庫,設(shè)置系統(tǒng)UI風(fēng)格译株,檢查是否需要顯示引導(dǎo)頁瓜喇、是否需要登錄、是否有新版本等歉糜,由于歷史原因乘寒,這里的代碼容易變得比較龐大,啟動(dòng)耗時(shí)難以控制匪补。
所以伞辛,滿足業(yè)務(wù)需要的前提下,didFinishLaunchingWithOptions在主線程里做的事情越少越好夯缺。在這一步蚤氏,我們可以做的優(yōu)化有:
1.梳理各個(gè)二方/三方庫,找到可以延遲加載的庫踊兜,做延遲加載處理竿滨,比如放到首頁控制器的2.viewDidAppear方法里。
3.梳理業(yè)務(wù)邏輯捏境,把可以延遲執(zhí)行的邏輯于游,做延遲執(zhí)行處理。比如檢查新版本垫言、注冊推送通知等邏輯贰剥。
4.避免復(fù)雜/多余的計(jì)算。
5.避免在首頁控制器的viewDidLoad和viewWillAppear做太多事情筷频,這2個(gè)方法執(zhí)行完蚌成,首頁控制器才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理凛捏。
6.采用性能更好的API担忧。
7.首頁控制器用純代碼方式來構(gòu)建。
二葵袭、循環(huán)引用(如下常見舉例)
1、對象相互引用無法釋放
2乖菱、block(block里面引用自身對象需要用__weak typedeof(self) weakSelf = self)
? ? ? VC1——》push到VC2
? ? ? VC2中命名block
? ? ?Self.block = ^{
執(zhí)行 5s后打印weakSelf.str
};
若在5s內(nèi)從VC2返回VC1坡锡,則VC2的block執(zhí)行的內(nèi)容結(jié)果為null
若在5s后從VC2返回VC1,則VC2的block執(zhí)行的結(jié)果為真實(shí)打印出self.str的值
從而為了避免上面的情況的發(fā)生則需要用__Strong來修飾self窒所。
三鹉勒、內(nèi)存泄漏檢測
一般4種方法
1、靜態(tài)檢測analyze
2吵取、instrument
3禽额、leaks(三方工具M(jìn)LeaksFinder)
4、dealloc方法檢測頁面銷毀的時(shí)候是否釋放對應(yīng)的對象
四、算法復(fù)雜度&函數(shù)性能測試(結(jié)合單元測試)
具體可以參考單元測試?yán)锩娴臏y試代碼執(zhí)行時(shí)間方法脯倒。
五实辑、imageNamed:和imageWithContentsOfFile:加載圖片的選擇
imageNamed:
這個(gè)方法用一個(gè)指定的名字在系統(tǒng)緩存中查找并返回一個(gè)圖片對象如果它存在的話。如果緩存中沒有找到相應(yīng)的圖片藻丢,這個(gè)方法從指定的文檔中加載然后緩存并返回這個(gè)對象剪撬。因此imageNamed的優(yōu)點(diǎn)是當(dāng)加載時(shí)會(huì)緩存圖片。所以當(dāng)圖片會(huì)頻繁的使用時(shí)悠反,那么用imageNamed的方法會(huì)比較好残黑。例如:你需要在 一個(gè)TableView里的TableViewCell里都加載同樣一個(gè)圖標(biāo),那么用imageNamed加載圖像效率很高斋否。系統(tǒng)會(huì)把那個(gè)圖標(biāo)Cache到內(nèi)存梨水,在TableViewCell里每次利用那個(gè)圖 像的時(shí)候,只會(huì)把圖片指針指向同一塊內(nèi)存茵臭。正是因此使用imageNamed會(huì)緩存圖片疫诽,即將圖片的數(shù)據(jù)放在內(nèi)存中,iOS的內(nèi)存非常珍貴并且在內(nèi)存消耗過大時(shí)笼恰,會(huì)強(qiáng)制釋放內(nèi)存踊沸,即會(huì)遇到memory warnings。而在iOS系統(tǒng)里面釋放圖像的內(nèi)存是一件比較麻煩的事情社证,有可能會(huì)造成內(nèi)存泄漏逼龟。例如:當(dāng)一 個(gè)UIView對象的animationImages是一個(gè)裝有UIImage對象動(dòng)態(tài)數(shù)組NSMutableArray,并進(jìn)行逐幀動(dòng)畫追葡。當(dāng)使用imageNamed的方式加載圖像到一個(gè)動(dòng)態(tài)數(shù)組NSMutableArray腺律,這將會(huì)很有可能造成內(nèi)存泄露。原因很顯然的宜肉。
imageWithContentsOfFile:
僅加載圖片匀钧,圖像數(shù)據(jù)不會(huì)緩存。因此對于較大的圖片以及使用情況較少時(shí)谬返,那就可以用該方法之斯,降低內(nèi)存消耗。
六遣铝、本地圖片資源的優(yōu)化
1佑刷、清除與項(xiàng)目無關(guān)的圖片資源,比如測試階段使用到酿炸、前期使用到瘫絮、前后期更換,上線以后就沒有用途了填硕, 刪除這些圖片麦萤。
2鹿鳖、對項(xiàng)目所有的引用圖片資源統(tǒng)一進(jìn)行壓縮后再使用。(https://tinypng.com)可以在這個(gè)網(wǎng)站進(jìn)行圖片壓縮壮莹。