App優(yōu)化

一、啟動(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)行圖片壓縮。

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

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

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