iOS性能優(yōu)化

最近采用Instruments 來分析整個(gè)應(yīng)用程序的性能.發(fā)現(xiàn)很多有意思的點(diǎn)邑茄,以及性能優(yōu)化和一些分析性能消耗的技巧.小結(jié)如下.

Instruments使用技巧

關(guān)于Instruments官方有一個(gè)很有用的用戶使用Guide,當(dāng)然如果不習(xí)慣官方英文可以在這里找到中文本翻譯版本PDF參閱.Instruments 確實(shí)是一個(gè)很強(qiáng)大的工具劣纲,用它來收集關(guān)于一個(gè)或多個(gè)系統(tǒng)進(jìn)程的性能和行為的數(shù)據(jù)極為方便,并能及時(shí)跟蹤隨著時(shí)間產(chǎn)生的數(shù)據(jù).還可以廣泛收集不同類型的數(shù)據(jù).關(guān)于Instrument工具基本使用不在贅述.如下重點(diǎn)說明一些使用技巧.

1.概覽

工具通過Xcode工具欄中Product->Profile可以啟動(dòng),啟動(dòng)后界面如下:

Instrument概覽[via by chenkai]

當(dāng)點(diǎn)擊Time Profiler應(yīng)用程序開始運(yùn)行后.就能獲取到整個(gè)應(yīng)用程序運(yùn)行消耗時(shí)間分布和百分比.為了保證數(shù)據(jù)分析在統(tǒng)一使用場(chǎng)景真實(shí)行有如下點(diǎn)需要注意:

在開始進(jìn)行應(yīng)用程序性能分析的時(shí)候,一定要使用真機(jī),模擬器運(yùn)行在Mac上眷细,然而Mac上的CPU往往比iOS設(shè)備要快鸠按。相反醒陆,Mac上的GPU和iOS設(shè)備的完全不一樣,模擬器不得已要在軟件層面(CPU)模擬設(shè)備的GPU葫掉,這意味著GPU相關(guān)的操作在模擬器上運(yùn)行的更慢些举,尤其是使用CAEAGLLayer來寫一些OpenGL的代碼時(shí)候. 這就導(dǎo)致模擬器性能數(shù)據(jù)和用戶真機(jī)使用性能數(shù)據(jù)相去甚運(yùn).

另外在開始性能分析前另外一件重要的事情是,應(yīng)用程序運(yùn)行一定要發(fā)布配置不是調(diào)試配置.

在發(fā)布環(huán)境打包的時(shí)候俭厚,編譯器會(huì)引入一系列提高性能的優(yōu)化户魏,例如去掉調(diào)試符號(hào)或者移除并重新組織代碼.另iOS引入一種"Watch Dog"[看門狗]機(jī)制.不同的場(chǎng)景下,“看門狗”會(huì)監(jiān)測(cè)應(yīng)用的性能挪挤。如果超出了該場(chǎng)景所規(guī)定的運(yùn)行時(shí)間叼丑,“看門狗”就會(huì)強(qiáng)制終結(jié)這個(gè)應(yīng)用的進(jìn)程.開發(fā)者可以crashlog看到對(duì)應(yīng)的日志.但Xcode在調(diào)試配置下會(huì)禁用"Watch Dog".

2.Time Profiler

選擇Time Profiler啟動(dòng).

time profile時(shí)間分析工具用來檢測(cè)應(yīng)用CPU的使用情況.可以看到應(yīng)用程序中各個(gè)方法正在消耗CPU時(shí)間.使用大量CPU不一定是個(gè)問題.類似我們客戶端中不同場(chǎng)景的天氣動(dòng)畫[類似大雨]的路徑就對(duì)CPU依賴就非常高,動(dòng)畫本身也是非晨该牛苛刻且耗費(fèi)資源較多的任務(wù).

點(diǎn)擊Record 開始運(yùn)行.

Time Profile 分析界面[via by chenkai]

剛開始我們拿到分析數(shù)據(jù)時(shí)往往是這樣的:

性能數(shù)據(jù)[via by chenkai]

這里顯示的是執(zhí)行代碼完整路徑鸠信,其中系統(tǒng)和應(yīng)用本身一些調(diào)用路徑完全揉捏在一起.完全看不到我們關(guān)心的應(yīng)用程序中實(shí)際代碼執(zhí)行耗時(shí)和代碼路徑實(shí)際所在位置.簡(jiǎn)單的方式可以快速勾選右邊Call Tree中Separate Thread和Hide System Libraries兩個(gè)選項(xiàng)[后面會(huì)解釋選項(xiàng)作用]:

拆分后性能數(shù)據(jù)[via by chenkai]

可以看到直接能夠看到應(yīng)用程序各個(gè)方法調(diào)用耗時(shí)直接路徑,剔除掉了系統(tǒng)相關(guān)方法和反向調(diào)用樹路徑.清爽很多.如果覺得這還不夠直觀,選擇任意一個(gè)耗時(shí)方法分支[這里選擇WeatherViewController viewDidLoad]雙擊進(jìn)入會(huì)看到:

代碼&耗時(shí)詳情

可以直接定位到viewDidLoad的代碼,也可以直觀的看到改方法下調(diào)用其他方法耗時(shí)的時(shí)間.類似[self loadCityWeatherScroollerView]耗時(shí)是121x,x既耗時(shí)單位這里為ms毫秒.當(dāng)然如果直接在Instrument找到問題覺得不方便修改,可以直接點(diǎn)擊右上方Xcode按鈕會(huì)直接定位Xcode對(duì)應(yīng)調(diào)用方法入口.這樣很容易能夠快速定位代碼占用CPU最多的方法.也可以打開Xcode快速修改并重新運(yùn)行Profile來看修改后耗時(shí)前后對(duì)比.簡(jiǎn)單便捷.

這里對(duì)右側(cè)call tree選項(xiàng)有必要做一下說明[官方user guide翻譯]:

Separate By Thread:線程分離,只有這樣才能在調(diào)用路徑中能夠清晰看到占用CPU最大的線程.

Invert Call Tree:從上到下跟蹤堆棧信息.這個(gè)選項(xiàng)可以快捷的看到方法調(diào)用路徑最深方法占用CPU耗時(shí),比如FuncA{FunB{FunC}},勾選后堆棧以C->B->A把調(diào)用層級(jí)最深的C顯示最外面.?

Hide Missing Symbols:如果dSYM無法找到你的APP或者調(diào)用系統(tǒng)框架的話论寨,那么表中將看到調(diào)用方法名只能看到16進(jìn)制的數(shù)值,勾選這個(gè)選項(xiàng)則可以隱藏這些符號(hào)星立,便于簡(jiǎn)化分析數(shù)據(jù).

Hide System Libraries:這個(gè)就更有用了,勾選后耗時(shí)調(diào)用路徑只會(huì)顯示app耗時(shí)的代碼,性能分析普遍我們都比較關(guān)系自己代碼的耗時(shí)而不是系統(tǒng)的.基本是必選項(xiàng).注意有些代碼耗時(shí)也會(huì)納入系統(tǒng)層級(jí),可以進(jìn)行勾選前后前后對(duì)執(zhí)行路徑進(jìn)行比對(duì)會(huì)非常有用.

關(guān)于其他方法不再贅述.

性能分析&代碼優(yōu)化

我們這次性能優(yōu)化主要針對(duì)如下兩個(gè)使用場(chǎng)景:

A:應(yīng)用程序第一次啟動(dòng)到進(jìn)入天氣首頁(yè)的時(shí)間.

B:從后臺(tái)切到前臺(tái)天氣首頁(yè)占用時(shí)間.

在還沒有拿到性能分析數(shù)據(jù)之前,一直認(rèn)為第一次啟動(dòng)耗時(shí)主要浪費(fèi)AppDelegate中第三方框架初始化上[類似WeiBo&WeChat 相關(guān)SDK初始化調(diào)用].當(dāng)我們拿到實(shí)際性能數(shù)據(jù)耗時(shí)占用比時(shí)發(fā)現(xiàn)實(shí)際情況并非如此:

啟動(dòng)耗時(shí)

如上可以看到應(yīng)用程序啟動(dòng)初始化工作主要會(huì)在MJAppDelegate如下兩個(gè)方法展開:willFinishLaunchingWithOptionsdidFinishLaunchingWithOptions,其中第三方框架初始化工作主要是willFinishLaunchingWithOptions中完成的.而實(shí)際情況耗時(shí)占比非常小.基本可以忽略不計(jì).

而我們要優(yōu)化兩個(gè)啟動(dòng)時(shí)間場(chǎng)景,不同在于.第一次進(jìn)入應(yīng)用需要經(jīng)過新手教程葬凳、添加城市绰垂、請(qǐng)求城市數(shù)據(jù)、解析數(shù)據(jù)火焰、初始化天氣首頁(yè)UI元素并加載場(chǎng)景動(dòng)畫. 而從后臺(tái)進(jìn)入時(shí)則從本地存儲(chǔ)DT文件中解析天氣數(shù)據(jù)辕坝、初始化天氣首頁(yè)UI元素并加載天氣動(dòng)畫.

1.NSDateFormatter問題凸顯

針對(duì)這點(diǎn)重點(diǎn)分析應(yīng)用啟動(dòng)&天氣首頁(yè)耗時(shí). 在AB兩個(gè)場(chǎng)景均發(fā)現(xiàn)加載首頁(yè)元素發(fā)現(xiàn)如下問題:

NSDate(TimeAgo)getDateStrByTimeZone耗時(shí)

繼續(xù)跟蹤發(fā)現(xiàn):

NSDate耗時(shí)

在AB兩個(gè)場(chǎng)景里均出現(xiàn)加載MJLineChartView 和 TendencyChartView 時(shí)獲取時(shí)區(qū)對(duì)應(yīng)時(shí)間上耗時(shí)較大.而耗時(shí)主要在getDateStrByTimeZone這個(gè)方法調(diào)用上.

getDateStrByTimeZone方法

其中創(chuàng)建一個(gè)NSDateFormatter對(duì)象平均耗時(shí)33ms左右 而設(shè)置NSDateFormatter的3個(gè)屬性平均耗時(shí)也在30ms左右.因?yàn)槭醉?yè)24小時(shí)天氣和未來幾天預(yù)報(bào)中.需要for循環(huán)中遍歷數(shù)據(jù),導(dǎo)致這個(gè)方法別重復(fù)調(diào)用多次荐健,則消耗時(shí)間不斷疊加.

針對(duì)這個(gè)問題:

NSDateFormatter對(duì)象本身初始化很慢,同樣還有NSCalendar也是如此.然而在一些使用場(chǎng)景中不可避免要使用他們,比如Json數(shù)據(jù)解析中.使用這個(gè)對(duì)象同時(shí)避免其性能開銷帶來性能開銷,一般比較好的方式是通過添加屬性(推薦)或創(chuàng)建靜態(tài)變量保持該對(duì)象只被初始化一次,而被多次復(fù)用.不得不值得一提的是設(shè)置一個(gè)NSDateFormatter屬性速度差不多是和創(chuàng)建新的實(shí)例對(duì)象一樣慢琳袄!

添加屬性方式如下:

屬性方式

針對(duì)NSDateFormatter時(shí)間開銷出了重用對(duì)象外江场,盡量避免采用其處理多個(gè)日期格式.當(dāng)然針對(duì)日期格式處理如果需要提高更多速度,可以直接采用C,可以采用第三方庫(kù)來規(guī)避這個(gè)問題..

2.UIImage緩存取舍

在項(xiàng)目代碼中看到大量使用如下代碼:

UIImage使用

在Main Thread中發(fā)現(xiàn)不同動(dòng)畫場(chǎng)景中Image IO 開銷和耗時(shí)所占比例均不一,在UIImage元素較多總體疊加耗時(shí)也會(huì)占用一定比例.內(nèi)存開銷也會(huì)明顯增高.

UIImage加載圖片方式一般有兩種:

A:imagedNamed初始化

B:imageWithContentsOfFile初始化

二者不同之處在于,imageNamed默認(rèn)加載圖片成功后會(huì)內(nèi)存中緩存圖片,這個(gè)方法用一個(gè)指定的名字在系統(tǒng)緩存中查找并返回一個(gè)圖片對(duì)象.如果緩存中沒有找到相應(yīng)的圖片對(duì)象,則從指定地方加載圖片然后緩存對(duì)象窖逗,并返回這個(gè)圖片對(duì)象.

而imageWithContentsOfFile則僅只加載圖片,不緩存.

大量使用imageNamed方式會(huì)在不需要緩存的地方額外增加開銷CPU的時(shí)間來做這件事.當(dāng)應(yīng)用程序需要加載一張比較大的圖片并且使用一次性址否,那么其實(shí)是沒有必要去緩存這個(gè)圖片的,用imageWithContentsOfFile是最為經(jīng)濟(jì)的方式,這樣不會(huì)因?yàn)閁IImage元素較多情況下,CPU會(huì)被逐個(gè)分散在不必要緩存上浪費(fèi)過多時(shí)間.

使用場(chǎng)景需要編程時(shí)佑附,應(yīng)該根據(jù)實(shí)際應(yīng)用場(chǎng)景加以區(qū)分樊诺,UIimage雖小,但使用元素較多問題會(huì)有所凸顯.

3.天氣首頁(yè)加載策略

在AB兩種場(chǎng)景把性能數(shù)據(jù)對(duì)比分析發(fā)現(xiàn):

天氣首頁(yè)WeatherView更新耗時(shí)

天氣首頁(yè)WeatherView初始化耗時(shí)一直300ms-450ms之間,占據(jù)首頁(yè)耗時(shí)很大一部分.且一直固定的開銷.占據(jù)Main Thread3分之一.

而用戶進(jìn)入最先看到是天氣首頁(yè)上半部分:

上半部分

而下半部分需要滾動(dòng)才能看到下半部分.且不一定觸發(fā):

下半部分

而現(xiàn)在整個(gè)首頁(yè)View的初始化和更新全部放到主線程來做.其中WeatherInfoView updateAllInfo方法更新耗時(shí)最長(zhǎng).更多的view意味著更多的渲染音同,也就是意味更多的CPU和內(nèi)存消耗词爬,對(duì)于我們天氣首頁(yè)在UIScrollView里邊嵌套了很多view更是如此

而針對(duì)這種情況不要在主線程承載過多的操作.uikit渲染,用戶輸入回應(yīng)都需要主進(jìn)程上完成.主線程被意外block或者加載響應(yīng)耗時(shí)過多都會(huì)影響到用戶體驗(yàn).而針對(duì)資源消耗過大操作权均,處理原則是最小化主線程的CPU占用顿膨,將工作“搬離”主線程, 不要阻塞主線程.類似本地一些IO完全移到其他線程來做.

調(diào)試time profiler過程中發(fā)現(xiàn),即使占用了很少的CPU時(shí)間(如果你在Time Profiler中看到這些的數(shù)據(jù))叽赊,也可能會(huì)阻塞主線程恋沃。磁盤、網(wǎng)絡(luò)必指、Lock囊咏、dispatch_sync以及向其它進(jìn)程/線程發(fā)送消息都會(huì)阻塞主線 程。Time Profiler只能檢測(cè)出占用CPU過多的堆棧塔橡,但檢測(cè)不了這些IO的問題.很奇怪.在System Trace里面突然發(fā)現(xiàn)了CPU Time很低梅割,但Wait Time很高的調(diào)用,說明在主線程處理I/O已經(jīng)嚴(yán)重?fù)p害了app的性能,這個(gè)時(shí)候考慮把這個(gè)操作優(yōu)化了.

而針對(duì)我們應(yīng)用首頁(yè)ui中多個(gè)view谱邪,在加載策略完全可以采用多線程進(jìn)行同步加載,只把上半部分放在主線程中加載炮捧,下班可以同時(shí)開一個(gè)線程進(jìn)行同步加載.這樣可以大大降低組線程初始化和更新時(shí)間,當(dāng)首頁(yè)初始化完畢已經(jīng)呈現(xiàn)是惦银,下半部分其實(shí)已經(jīng)另外一個(gè)線程處理完畢.

另外針對(duì)單個(gè)view 盡量不要在viewWillAppear費(fèi)時(shí)的操作咆课,viewWillAppear在 view 顯示之前被調(diào)用,出于效率考慮扯俱,在這個(gè)方法中不要處理復(fù)雜費(fèi)時(shí)的事情书蚪;只應(yīng)該在這個(gè)方法設(shè)置 view 的顯示屬性之類的簡(jiǎn)單事情,比如背景色迅栅,字體等殊校。不然,用戶會(huì)明顯感覺到 view 顯示遲鈍.

4:應(yīng)用首次加載時(shí)間

應(yīng)用首次啟動(dòng)加載操作:

首次加載

首次加載坐了如下操作:

A: 鏈接和載入:可以在Time Profile中顯示dyld載入庫(kù)函數(shù)读存,庫(kù)會(huì)被映射到地址空間为流,同時(shí)完成綁定以及靜態(tài)初始化.

B: UIKit初始化:如果應(yīng)用的Root View Controller是由XIB實(shí)現(xiàn)的,也會(huì)在啟動(dòng)時(shí)被初始化.

C: 應(yīng)用回調(diào):調(diào)用UIApplicationDeleagte的回調(diào):application:didFinishLaunchingWithOptions.

D: 第一次Core Animation調(diào)用:在啟動(dòng)后的方法-[UIApplication _resportAppLaunchFinished]中調(diào)用CA::Transaction::commit實(shí)現(xiàn)第一幀畫面的繪制.

應(yīng)用程序首次加載中啟動(dòng)方法willFinishLaunchingWithOptions和didFinishLaunchingWithOptions只做應(yīng)用程序首次啟動(dòng)必須的要操作,而針對(duì)_dyid_start在初始化庫(kù)framework函數(shù)的操作.不必要的Framework不要鏈接让簿,避免首次加載耗時(shí).

小結(jié)如上.很多地方代碼調(diào)用和底層機(jī)制看的不是特別明白,整理總結(jié)關(guān)于優(yōu)化部分實(shí)在有限敬察,如上僅供各位參考.另外Instruments確實(shí)是把分析代碼利器.目前沒有任何一個(gè)第三方工具可以去替代.推薦各位使用.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尔当,隨后出現(xiàn)的幾起案子莲祸,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锐帜,死亡現(xiàn)場(chǎng)離奇詭異田盈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缴阎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門允瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人药蜻,你說我怎么就攤上這事瓷式。” “怎么了语泽?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵贸典,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我踱卵,道長(zhǎng)廊驼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任惋砂,我火速辦了婚禮妒挎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘西饵。我一直安慰自己酝掩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布眷柔。 她就那樣靜靜地躺著期虾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驯嘱。 梳的紋絲不亂的頭發(fā)上镶苞,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音鞠评,去河邊找鬼茂蚓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛剃幌,可吹牛的內(nèi)容都是我干的聋涨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼负乡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼牛郑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起敬鬓,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钉答,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體础芍,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年数尿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仑性。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡右蹦,死狀恐怖诊杆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情何陆,我是刑警寧澤晨汹,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站贷盲,受9級(jí)特大地震影響淘这,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜巩剖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一铝穷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧佳魔,春花似錦曙聂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至镊尺,卻和暖如春朦佩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庐氮。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工语稠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人弄砍。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓仙畦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親音婶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子慨畸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容