一、啟動(dòng)時(shí)間測(cè)量(優(yōu)化啟動(dòng)時(shí)間)
1、main函數(shù)前執(zhí)行的時(shí)間
在此階段系統(tǒng)做的任務(wù)為:
1.1. 加載應(yīng)用的可執(zhí)行文件
1.2. 加載動(dòng)態(tài)鏈接庫(kù)加載器dyld(dynamic loader)
1.3. dyld遞歸加載應(yīng)用所有依賴(lài)的dylib(dynamic library 動(dòng)態(tài)鏈接庫(kù))
測(cè)試方法:在 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)用依賴(lài)的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)化。
所以,依賴(lài)的dylib越少越好。在這一步,我們可以做的優(yōu)化有:
盡量不使用內(nèi)嵌(embedded)的dylib,加載內(nèi)嵌dylib性能開(kāi)銷(xiāo)較大
合并已有的dylib和使用靜態(tài)庫(kù)(static archives),減少dylib的使用個(gè)數(shù)
懶加載dylib,但是要注意dlopen()可能造成一些問(wèn)題,且實(shí)際上懶加載做的工作會(huì)更多
?Rebase/Bind
在dylib的加載過(guò)程中,系統(tǒng)為了安全考慮,引入了ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名。由于A(yíng)SLR的存在,鏡像(Image,包括可執(zhí)行文件、dylib和bundle)會(huì)在隨機(jī)的地址上加載,和之前指針指向的地址(preferred_address)會(huì)有一個(gè)偏差(slide),dyld需要修正這個(gè)偏差,來(lái)指向正確的地址。
Rebase在前,Bind在后,Rebase做的是將鏡像讀入內(nèi)存,修正鏡像內(nèi)部的指針,性能消耗主要在IO。Bind做的是查詢(xún)符號(hào)表,設(shè)置指向鏡像外部的指針,性能消耗主要在CPU計(jì)算。所以,指針數(shù)量越少越好。
在這一步,我們可以做的優(yōu)化有:
減少ObjC類(lèi)(class)、方法(selector)、分類(lèi)(category)的數(shù)量
減少C++虛函數(shù)的的數(shù)量(創(chuàng)建虛函數(shù)表有開(kāi)銷(xiāo))
使用Swift structs(內(nèi)部做了優(yōu)化,符號(hào)數(shù)量更少)
Objc setup
大部分ObjC初始化工作已經(jīng)在Rebase/Bind階段做完了,這一步dyld會(huì)注冊(cè)所有聲明過(guò)的ObjC類(lèi),將分類(lèi)插入到類(lèi)的方法列表里,再檢查每個(gè)selector的唯一性。
在這一步倒沒(méi)什么優(yōu)化可做的,Rebase/Bind階段優(yōu)化好了,這一步的耗時(shí)也會(huì)減少。
?Initializers
到了這一階段,dyld開(kāi)始運(yùn)行程序的初始化函數(shù),調(diào)用每個(gè)Objc類(lèi)和分類(lèi)的+load方法,調(diào)用C/C++ 中的構(gòu)造器函數(shù)(用attribute((constructor))修飾的函數(shù)),和創(chuàng)建非基本類(lèi)型的C++靜態(tài)全局變量。Initializers階段執(zhí)行完后,dyld開(kāi)始調(diào)用main()函數(shù)。
在這一步,我們可以做的優(yōu)化有:?
1.少在類(lèi)的+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
測(cè)試方法:
在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方法讓其可見(jiàn)。由于業(yè)務(wù)需要,我們會(huì)初始化各個(gè)二方/三方庫(kù),設(shè)置系統(tǒng)UI風(fēng)格,檢查是否需要顯示引導(dǎo)頁(yè)、是否需要登錄、是否有新版本等,由于歷史原因,這里的代碼容易變得比較龐大,啟動(dòng)耗時(shí)難以控制。
所以,滿(mǎn)足業(yè)務(wù)需要的前提下,didFinishLaunchingWithOptions在主線(xiàn)程里做的事情越少越好。在這一步,我們可以做的優(yōu)化有:
1.梳理各個(gè)二方/三方庫(kù),找到可以延遲加載的庫(kù),做延遲加載處理,比如放到首頁(yè)控制器的2.viewDidAppear方法里。
3.梳理業(yè)務(wù)邏輯,把可以延遲執(zhí)行的邏輯,做延遲執(zhí)行處理。比如檢查新版本、注冊(cè)推送通知等邏輯。
4.避免復(fù)雜/多余的計(jì)算。
5.避免在首頁(yè)控制器的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完,首頁(yè)控制器才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理。
6.采用性能更好的API。
7.首頁(yè)控制器用純代碼方式來(lái)構(gòu)建。
二、循環(huán)引用(如下常見(jiàn)舉例)
1、對(duì)象相互引用無(wú)法釋放
2、block(block里面引用自身對(duì)象需要用__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來(lái)修飾self。
三、內(nèi)存泄漏檢測(cè)
一般4種方法
1、靜態(tài)檢測(cè)analyze
2、instrument
3、leaks(三方工具M(jìn)LeaksFinder)
4、dealloc方法檢測(cè)頁(yè)面銷(xiāo)毀的時(shí)候是否釋放對(duì)應(yīng)的對(duì)象
四、算法復(fù)雜度&函數(shù)性能測(cè)試(結(jié)合單元測(cè)試)
具體可以參考單元測(cè)試?yán)锩娴臏y(cè)試代碼執(zhí)行時(shí)間方法。
五、imageNamed:和imageWithContentsOfFile:加載圖片的選擇
imageNamed:
這個(gè)方法用一個(gè)指定的名字在系統(tǒng)緩存中查找并返回一個(gè)圖片對(duì)象如果它存在的話(huà)。如果緩存中沒(méi)有找到相應(yīng)的圖片,這個(gè)方法從指定的文檔中加載然后緩存并返回這個(gè)對(duì)象。因此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)存消耗過(guò)大時(shí),會(huì)強(qiáng)制釋放內(nèi)存,即會(huì)遇到memory warnings。而在iOS系統(tǒng)里面釋放圖像的內(nèi)存是一件比較麻煩的事情,有可能會(huì)造成內(nèi)存泄漏。例如:當(dāng)一 個(gè)UIView對(duì)象的animationImages是一個(gè)裝有UIImage對(duì)象動(dòng)態(tài)數(shù)組NSMutableArray,并進(jìn)行逐幀動(dòng)畫(huà)。當(dāng)使用imageNamed的方式加載圖像到一個(gè)動(dòng)態(tài)數(shù)組NSMutableArray,這將會(huì)很有可能造成內(nèi)存泄露。原因很顯然的。
imageWithContentsOfFile:
僅加載圖片,圖像數(shù)據(jù)不會(huì)緩存。因此對(duì)于較大的圖片以及使用情況較少時(shí),那就可以用該方法,降低內(nèi)存消耗。
六、本地圖片資源的優(yōu)化
1、清除與項(xiàng)目無(wú)關(guān)的圖片資源,比如測(cè)試階段使用到、前期使用到、前后期更換,上線(xiàn)以后就沒(méi)有用途了, 刪除這些圖片。
2、對(duì)項(xiàng)目所有的引用圖片資源統(tǒng)一進(jìn)行壓縮后再使用。(https://tinypng.com)可以在這個(gè)網(wǎng)站進(jìn)行圖片壓縮。