iOS 性能優化:Instruments 工具的救命三招


你的 iOS 應用,運行速度靠譜嗎?中槍的同學莫要愁,性能優化咱有妙招。用 Xcode 自家的調試工具 Instruments,揪出那些堵線程、占內存、耗資源的問題代碼,徹底破掉迷局,讓應用揚眉吐氣!

對於每位 iOS 開發者來說,代碼性能是個避不開的話題。隨着項目的擴大和功能的增多,沒經過認真調試和優化的代碼,要么任性地卡頓運行,要么低調地崩潰了之……結果呢,大家用着不高興,開發者也不開心。

其實要破這個局面並不難,只要在 Xcode 自帶的監控調試工具 Instruments 上花點功夫,讓大代碼流暢運行也不是神話。Instruments 提供了很多功能,我會重點介紹一下我最常用的三大類:

  • Time Profiler:分析代碼的執行時間,找出導致程序變慢的原因。
  • Allocations:監測內存使用/分配情況
    迅速膨脹的內存可以很快讓程序斃命,所以要多加防范。
  • Leaks:找到引發內存泄漏的起點

即使有 ARC(自動引用計數)內存管理機制,但在現實中對象之間引用復雜,循環引用導致的內存泄漏仍然難以避免,所以關鍵時刻還要自力更生。

針對這三方面的測試,我寫了個演示應用,放在 GitHub 上,來幫助大家更直觀地了解這些工具的使用方法。好,進入正題。

 

 

Time Profiler

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

/* 引用自:http://waynewbishop.com/swift/sorting-algorithms/ */ func insertionSort() { var x, y, key: Int for (x = 0; x < numberList.count; x++) { key = numberList[x] for (y = x; y > -1; y--) { if key < numberList[y] { numberList.removeAtIndex(y + 1) numberList.insert(key, atIndex: y) } } } } func bubbleSort() { var x, y, z, passes, key : Int for (x = 0; x < numberList.count; ++x) { passes = (numberList.count - 1) - x; for (y = 0; y < passes; y++) { key = numberList[y] if (key > numberList[y + 1]) { z = numberList[y + 1] numberList[y + 1] = key numberList[y] = z } } } } 

這段代碼主要是對數組的添加和刪除,兩種方法執行起來耗時不多,但后台發生的系統動作卻多得讓人眼暈。

可以發現,代碼用到了很多間接依賴,這些都是支撐代碼運行的系統庫文件。因為處理大數據集比較消耗系統資源,所以要盡可能地把繁重的操作放到后台去做,上面的代碼就走的后台線程。在上圖的 Call Tree 中可以看到,被調用的堆棧名是 dispatch_worker_thread3。如果把它放到主線程去執行,程序肯定會掛起。不信你注釋掉 dispatch_async 調用看一下。

再來個圖片加載的例子。

這兒有三種圖片加載方法:

  • loadSlowImage1:從指定 URL 下載一張圖片(加載速度慢)
  • loadImage2:從本地資源庫加載一張圖片(注意:沒用系統緩存)
  • loadFastImage3:從系統緩存中加載一張圖片(加載速度快)

我們來看看 Time Profiler 算出的結果是不是跟預想的一樣。

進入演示應用第二項「Time Profiler: Our Methods」,點擊「Reload」十次來重復加載圖片,這樣能產生足夠的數據來分析。然后在 Time Profiler 圖表中通過拖拉鼠標選中要放大查看的區域,從 Call Tree 中雙擊調用了 .reload 方法那一行(上圖中加亮選中那一行),就會跳轉到對應的代碼行,所用時間也標注出來了。

看到誰最花時間了吧。雖然代碼沒什么可優化的地方,但大家應該認識到緩存能發揮的作用。所以即使有時還得調用 loadSlowImage,多數情況下把圖片緩存下來,還是能省些資源占用。

此外,我想再說說 Call Tree 的選項設置。

這些選項默認是不選的,但把它們勾選上可以幫你更快定位到關鍵的代碼上,往往這也是問題的源頭。

  • Separate by Thread:按線程分開做分析,這樣更容易揪出那些吃資源的問題線程。特別是對於主線程,它要處理和渲染所有的接口數據,一旦受到阻塞,程序必然卡頓或停止響應。
  • Invert Call Tree:反向輸出調用樹。把調用層級最深的方法顯示在最上面,更容易找到最耗時的操作。
  • Hide Missing Symbols:隱藏缺失符號。如果 dSYM 文件或其他系統架構缺失,列表中會出現很多奇怪的十六進制的數值,用此選項把這些干擾元素屏蔽掉,讓列表回歸清爽。
  • Hide System Libraries:隱藏系統庫文件。過濾掉各種系統調用,只顯示自己的代碼調用。
  • Flattern Recursion:拼合遞歸。將同一遞歸函數產生的多條堆棧(因為遞歸函數會調用自己)合並為一條。
  • Top Functions:找到最耗時的函數或方法。

需要添加其他工具的話:

 

Allocations

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

從圖中可以看到,每次點擊「Reload」重新載入圖片時,內存都會出現使用峰值。應用先分配大量內存來替換原有圖片,然后再釋放掉這部分內存,可想而知這樣的操作效率高不了,而且如果要下載更大的文件,呃,局面大概會失控吧。

看一下堆棧列表第四行,ImageIO_PNG_Data 里有 9 張處於活動狀態的圖片,占用了12.38 MB 內存,這些都是沒被系統釋放或緩存的內存,所以導致堆內存分配升高。接下來再看看使用緩存后的效果。

使用了緩存庫(Swift Haneke)后,點「Reload」五次,這回在 Allocations 列表中卻看不到 ImageIO_PNG_Data 對象了,這說明它是空的,沒有任何圖像數據。同時,All Heap Allocations 的大小已從剛才的 14.61 MB 降到了 2.51 MB。Anonymous VM(匿名虛擬內存)是系統為程序預留的、可能會立即被重復使用的一部分可用內存。要防止程序崩潰,就別讓堆的尺寸增長太快。

還有就是,例子用的是異步方式來加載圖片,這樣用不着等到所有圖片下載完才能在界面中顯示。大多數圖像緩存庫都會把加載工作放到后台,以避免延長主線程的響應周期。

Leaks

盡管 Apple 推出的 ARC 可以有效防范內存泄漏,但出問題的機率還是會有,Swift 也不例外。鑒於篇幅有限,本文就不涉及內存和 ARC 的工作原理了,具體可以參考官方文檔。我會用代碼來觸發內存泄漏。

首先從最底層上說,當兩個對象相互建立了強引用(strong reference),當一個對象被釋放,另一個對象由於是強引用的關系不允許被釋放,此時 ARC 無法確定沒被釋放的對象到底還有沒有用,於是就導致了內存泄漏。

要解決這個問題,可以將其中的一個對象中變量設為 weak,不讓它出現在保留周期中。很多開發者在管理 view controller 時常會在內存泄漏上中招,以為換了新的 controller,老的 controller 就被釋放回收了,其實還沒。這樣代碼一多,就會造成很多對象都沒被釋放。所以用這個工具把整個應用跑一遍,把那些斷鏈的強引用清理干凈,會大有裨益。

除了上述這三類工具,Instruments 還有很多實用的工具,推薦大家根據自己的關注點,花些時間去學學。比如:

  • Core Data:監測讀取、緩存未命中、保存等操作,能直觀顯示是否保存次數遠超實際需要。
  • Cocoa Layout:觀察約束變化,找出布局代碼的問題所在。
  • Network:跟蹤 TCP / IP和 UDP / IP 連接。
  • Automations:創建和編輯測試腳本來自動化 iOS 應用的用戶界面測試。

最后小總結下。我倒不想一味誇大 Instruments 的作用,如果應用跑得挺痛快,沒出現啥調皮行為,大可把它忽略,等到問題來了再做優化。對於新手來說,花些時間了解 Instruments 的功能,多調試多積累經驗,這樣做出來的應用在用戶體驗上肯定錯不了。

你最常用的 Instruments 工具都有哪些?歡迎與我們分享。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM