你的 iOS 應(yīng)用,運(yùn)行速度靠譜嗎?中槍的同學(xué)莫要愁,性能優(yōu)化咱有妙招。用 Xcode 自家的調(diào)試工具 Instruments,揪出那些堵線程、占內(nèi)存、耗資源的問題代碼,徹底破掉迷局,讓應(yīng)用揚(yáng)眉吐氣!
對于每位 iOS 開發(fā)者來說,代碼性能是個避不開的話題。隨著項目的擴(kuò)大和功能的增多,沒經(jīng)過認(rèn)真調(diào)試和優(yōu)化的代碼,要么任性地卡頓運(yùn)行,要么低調(diào)地崩潰了之……結(jié)果呢,大家用著不高興,開發(fā)者也不開心。
其實要破這個局面并不難,只要在 Xcode 自帶的監(jiān)控調(diào)試工具 Instruments 上花點功夫,讓大代碼流暢運(yùn)行也不是神話。Instruments 提供了很多功能,我會重點介紹一下我最常用的三大類:
Time Profiler:分析代碼的執(zhí)行時間,找出導(dǎo)致程序變慢的原因。
Allocations:監(jiān)測內(nèi)存使用/分配情況
迅速膨脹的內(nèi)存可以很快讓程序斃命,所以要多加防范。
Leaks:找到引發(fā)內(nèi)存泄漏的起點
即使有 ARC(自動引用計數(shù))內(nèi)存管理機(jī)制,但在現(xiàn)實中對象之間引用復(fù)雜,循環(huán)引用導(dǎo)致的內(nèi)存泄漏仍然難以避免,所以關(guān)鍵時刻還要自力更生。
針對這三方面的測試,我寫了個演示應(yīng)用,放在GitHub上面, 來幫助大家更直觀地了解這些工具的使用方法。

好,進(jìn)入正題。
Time Profiler
時間都去哪兒啦? Time Profiler 可以回答。它會按照設(shè)定的時間間隔(默認(rèn) 1 毫秒)來跟蹤每一線程的堆棧信息(stack trace),并通過比較時間間隔之間的堆棧狀態(tài),來推算出某個方法執(zhí)行了多久,給出一個近似值。 在演示應(yīng)用頭一項「Time Profiler: System Methods」中,我用插入排序(Insertion Sort)和冒泡排序(Bubble Sort)兩種算法來做性能比較,下面是 Swift 代碼:

這段代碼主要是對數(shù)組的添加和刪除,兩種方法執(zhí)行起來耗時不多,但后臺發(fā)生的系統(tǒng)動作卻多得讓人眼暈。

可以發(fā)現(xiàn),代碼用到了很多間接依賴,這些都是支撐代碼運(yùn)行的系統(tǒng)庫文件。因為處理大數(shù)據(jù)集比較消耗系統(tǒng)資源,所以要盡可能地把繁重的操作放到后臺去做,上面的代碼就走的后臺線程。在上圖的 Call Tree 中可以看到,被調(diào)用的堆棧名是 dispatch_worker_thread3。如果把它放到主線程去執(zhí)行,程序肯定會掛起。不信你注釋掉 dispatch_async 調(diào)用看一下。
再來個圖片加載的例子。
這兒有三種圖片加載方法:
loadSlowImage1:從指定 URL 下載一張圖片(加載速度慢)
loadImage2:從本地資源庫加載一張圖片(注意:沒用系統(tǒng)緩存)
loadFastImage3:從系統(tǒng)緩存中加載一張圖片(加載速度快)
我們來看看 Time Profiler 算出的結(jié)果是不是跟預(yù)想的一樣。
進(jìn)入演示應(yīng)用第二項「Time Profiler: Our Methods」,點擊「Reload」十次來重復(fù)加載圖片,這樣能產(chǎn)生足夠的數(shù)據(jù)來分析。然后在 Time Profiler 圖表中通過拖拉鼠標(biāo)選中要放大查看的區(qū)域,從 Call Tree 中雙擊調(diào)用了 .reload 方法那一行(上圖中加亮選中那一行),就會跳轉(zhuǎn)到對應(yīng)的代碼行,所用時間也標(biāo)注出來了。

看到誰最花時間了吧。雖然代碼沒什么可優(yōu)化的地方,但大家應(yīng)該認(rèn)識到緩存能發(fā)揮的作用。所以即使有時還得調(diào)用 loadSlowImage,多數(shù)情況下把圖片緩存下來,還是能省些資源占用。
此外,我想再說說 Call Tree 的選項設(shè)置。

這些選項默認(rèn)是不選的,但把它們勾選上可以幫你更快定位到關(guān)鍵的代碼上,往往這也是問題的源頭。
Separate by Thread:按線程分開做分析,這樣更容易揪出那些吃資源的問題線程。特別是對于主線程,它要處理和渲染所有的接口數(shù)據(jù),一旦受到阻塞,程序必然卡頓或停止響應(yīng)。
Invert Call Tree:反向輸出調(diào)用樹。把調(diào)用層級最深的方法顯示在最上面,更容易找到最耗時的操作。
Hide Missing Symbols:隱藏缺失符號。如果 dSYM 文件或其他系統(tǒng)架構(gòu)缺失,列表中會出現(xiàn)很多奇怪的十六進(jìn)制的數(shù)值,用此選項把這些干擾元素屏蔽掉,讓列表回歸清爽。
Hide System Libraries:隱藏系統(tǒng)庫文件。過濾掉各種系統(tǒng)調(diào)用,只顯示自己的代碼調(diào)用。
Flattern Recursion:拼合遞歸。將同一遞歸函數(shù)產(chǎn)生的多條堆棧(因為遞歸函數(shù)會調(diào)用自己)合并為一條。
Top Functions:找到最耗時的函數(shù)或方法。
需要添加其他工具的話:

Allocations
我們經(jīng)常需要從服務(wù)器下載大量圖片,特別是開發(fā)照片類的應(yīng)用。但往往稍不注意,內(nèi)存使用就會暴增,所以得保證把這些圖片緩存下來以便重復(fù)使用。下面來看看演示程序中內(nèi)存分配的例子。

從圖中可以看到,每次點擊「Reload」重新載入圖片時,內(nèi)存都會出現(xiàn)使用峰值。應(yīng)用先分配大量內(nèi)存來替換原有圖片,然后再釋放掉這部分內(nèi)存,可想而知這樣的操作效率高不了,而且如果要下載更大的文件,呃,局面大概會失控吧。
看一下堆棧列表第四行,ImageIO_PNG_Data 里有 9 張?zhí)幱诨顒訝顟B(tài)的圖片,占用了12.38 MB 內(nèi)存,這些都是沒被系統(tǒng)釋放或緩存的內(nèi)存,所以導(dǎo)致堆內(nèi)存分配升高。接下來再看看使用緩存后的效果。

使用了緩存庫Swift Haneke后,點「Reload」五次,這回在 Allocations 列表中卻看不到 ImageIO_PNG_Data 對象了,這說明它是空的,沒有任何圖像數(shù)據(jù)。同時,All Heap Allocations 的大小已從剛才的 14.61 MB 降到了 2.51 MB。Anonymous VM(匿名虛擬內(nèi)存)是系統(tǒng)為程序預(yù)留的、可能會立即被重復(fù)使用的一部分可用內(nèi)存。要防止程序崩潰,就別讓堆的尺寸增長太快。
還有就是,例子用的是異步方式來加載圖片,這樣用不著等到所有圖片下載完才能在界面中顯示。大多數(shù)圖像緩存庫都會把加載工作放到后臺,以避免延長主線程的響應(yīng)周期。
Leaks
盡管 Apple 推出的 ARC 可以有效防范內(nèi)存泄漏,但出問題的機(jī)率還是會有,Swift 也不例外。鑒于篇幅有限,本文就不涉及內(nèi)存和 ARC 的工作原理了,具體可以參考官方文檔。我會用代碼來觸發(fā)內(nèi)存泄漏。
首先從最底層上說,當(dāng)兩個對象相互建立了強(qiáng)引用(strong reference),當(dāng)一個對象被釋放,另一個對象由于是強(qiáng)引用的關(guān)系不允許被釋放,此時 ARC 無法確定沒被釋放的對象到底還有沒有用,于是就導(dǎo)致了內(nèi)存泄漏。

要解決這個問題,可以將其中的一個對象中變量設(shè)為 weak,不讓它出現(xiàn)在保留周期中。很多開發(fā)者在管理 view controller 時常會在內(nèi)存泄漏上中招,以為換了新的 controller,老的 controller 就被釋放回收了,其實還沒。這樣代碼一多,就會造成很多對象都沒被釋放。所以用這個工具把整個應(yīng)用跑一遍,把那些斷鏈的強(qiáng)引用清理干凈,會大有裨益。
除了上述這三類工具,Instruments 還有很多實用的工具,推薦大家根據(jù)自己的關(guān)注點,花些時間去學(xué)學(xué)。比如:
Core Data:監(jiān)測讀取、緩存未命中、保存等操作,能直觀顯示是否保存次數(shù)遠(yuǎn)超實際需要。
Cocoa Layout:觀察約束變化,找出布局代碼的問題所在。
Network:跟蹤 TCP / IP和 UDP / IP 連接。
Automations:創(chuàng)建和編輯測試腳本來自動化 iOS 應(yīng)用的用戶界面測試。
最后小總結(jié)下。我倒不想一味夸大 Instruments 的作用,如果應(yīng)用跑得挺痛快,沒出現(xiàn)啥調(diào)皮行為,大可把它忽略,等到問題來了再做優(yōu)化。對于新手來說,花些時間了解 Instruments 的功能,多調(diào)試多積累經(jīng)驗,這樣做出來的應(yīng)用在用戶體驗上肯定錯不了。

上圖為2017年最新的視頻教程資料,搜索235再填上214最后輸入9755加我好友私聊我上傳視頻教程,有什么不懂的也可以來私聊問我。
不定時更新中。
如果你能明白這些視頻資料的好差,那么你也算是入行了,底層和中高層就是這一步之差。