https://segmentfault.com/a/1190000002568993
「原創(chuàng)譯文」iOS 性能優(yōu)化:Instruments 工具的救命三招
你的 iOS 應(yīng)用蹂季,運(yùn)行速度靠譜嗎冕广?中槍的同學(xué)莫要愁,性能優(yōu)化咱有妙招偿洁。用 Xcode 自家的調(diào)試工具 Instruments撒汉,揪出那些堵線程、占內(nèi)存涕滋、耗資源的問題代碼睬辐,徹底破掉迷局,讓應(yīng)用揚(yáng)眉吐氣!
對于每位 iOS 開發(fā)者來說溯饵,代碼性能是個(gè)避不開的話題侵俗。隨著項(xiàng)目的擴(kuò)大和功能的增多,沒經(jīng)過認(rèn)真調(diào)試和優(yōu)化的代碼丰刊,要么任性地卡頓運(yùn)行坡慌,要么低調(diào)地崩潰了之……結(jié)果呢,大家用著不高興藻三,開發(fā)者也不開心。
其實(shí)要破這個(gè)局面并不難跪者,只要在 Xcode 自帶的監(jiān)控調(diào)試工具 Instruments 上花點(diǎn)功夫棵帽,讓大代碼流暢運(yùn)行也不是神話。Instruments 提供了很多功能渣玲,我會重點(diǎn)介紹一下我最常用的三大類:
Time Profiler:分析代碼的執(zhí)行時(shí)間逗概,找出導(dǎo)致程序變慢的原因。
Allocations:監(jiān)測內(nèi)存使用/分配情況迅速膨脹的內(nèi)存可以很快讓程序斃命忘衍,所以要多加防范逾苫。
Leaks:找到引發(fā)內(nèi)存泄漏的起點(diǎn)
即使有 ARC(自動引用計(jì)數(shù))內(nèi)存管理機(jī)制,但在現(xiàn)實(shí)中對象之間引用復(fù)雜枚钓,循環(huán)引用導(dǎo)致的內(nèi)存泄漏仍然難以避免铅搓,所以關(guān)鍵時(shí)刻還要自力更生。
針對這三方面的測試搀捷,我寫了個(gè)演示應(yīng)用星掰,放在 GitHub 上,來幫助大家更直觀地了解這些工具的使用方法嫩舟。好氢烘,進(jìn)入正題。
Time Profiler
時(shí)間都去哪兒啦家厌? Time Profiler 可以回答播玖。它會按照設(shè)定的時(shí)間間隔(默認(rèn) 1 毫秒)來跟蹤每一線程的堆棧信息(stack trace),并通過比較時(shí)間間隔之間的堆棧狀態(tài)饭于,來推算出某個(gè)方法執(zhí)行了多久蜀踏,給出一個(gè)近似值。在演示應(yīng)用頭一項(xiàng)「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 } } }}
這段代碼主要是對數(shù)組的添加和刪除,兩種方法執(zhí)行起來耗時(shí)不多畴栖,但后臺發(fā)生的系統(tǒng)動作卻多得讓人眼暈随静。
可以發(fā)現(xiàn),代碼用到了很多間接依賴,這些都是支撐代碼運(yùn)行的系統(tǒng)庫文件燎猛。因?yàn)樘幚泶髷?shù)據(jù)集比較消耗系統(tǒng)資源恋捆,所以要盡可能地把繁重的操作放到后臺去做,上面的代碼就走的后臺線程重绷。在上圖的 Call Tree 中可以看到沸停,被調(diào)用的堆棧名是 dispatch_worker_thread3。如果把它放到主線程去執(zhí)行昭卓,程序肯定會掛起愤钾。不信你注釋掉 dispatch_async 調(diào)用看一下。
再來個(gè)圖片加載的例子候醒。
這兒有三種圖片加載方法:
loadSlowImage1:從指定 URL 下載一張圖片(加載速度慢)
loadImage2:從本地資源庫加載一張圖片(注意:沒用系統(tǒng)緩存)
loadFastImage3:從系統(tǒng)緩存中加載一張圖片(加載速度快)
我們來看看 Time Profiler 算出的結(jié)果是不是跟預(yù)想的一樣能颁。
進(jìn)入演示應(yīng)用第二項(xiàng)「Time Profiler: Our Methods」,點(diǎn)擊「Reload」十次來重復(fù)加載圖片倒淫,這樣能產(chǎn)生足夠的數(shù)據(jù)來分析伙菊。然后在 Time Profiler 圖表中通過拖拉鼠標(biāo)選中要放大查看的區(qū)域,從 Call Tree 中雙擊調(diào)用了 .reload 方法那一行(上圖中加亮選中那一行)敌土,就會跳轉(zhuǎn)到對應(yīng)的代碼行镜硕,所用時(shí)間也標(biāo)注出來了。
看到誰最花時(shí)間了吧返干。雖然代碼沒什么可優(yōu)化的地方兴枯,但大家應(yīng)該認(rèn)識到緩存能發(fā)揮的作用。所以即使有時(shí)還得調(diào)用 loadSlowImage犬金,多數(shù)情況下把圖片緩存下來念恍,還是能省些資源占用。
此外晚顷,我想再說說 Call Tree 的選項(xiàng)設(shè)置峰伙。
這些選項(xiàng)默認(rèn)是不選的,但把它們勾選上可以幫你更快定位到關(guān)鍵的代碼上该默,往往這也是問題的源頭瞳氓。
Separate by Thread:按線程分開做分析,這樣更容易揪出那些吃資源的問題線程栓袖。特別是對于主線程匣摘,它要處理和渲染所有的接口數(shù)據(jù),一旦受到阻塞裹刮,程序必然卡頓或停止響應(yīng)音榜。
Invert Call Tree:反向輸出調(diào)用樹。把調(diào)用層級最深的方法顯示在最上面捧弃,更容易找到最耗時(shí)的操作赠叼。
Hide Missing Symbols:隱藏缺失符號擦囊。如果 dSYM 文件或其他系統(tǒng)架構(gòu)缺失,列表中會出現(xiàn)很多奇怪的十六進(jìn)制的數(shù)值嘴办,用此選項(xiàng)把這些干擾元素屏蔽掉瞬场,讓列表回歸清爽。
Hide System Libraries:隱藏系統(tǒng)庫文件涧郊。過濾掉各種系統(tǒng)調(diào)用贯被,只顯示自己的代碼調(diào)用。
Flattern Recursion:拼合遞歸妆艘。將同一遞歸函數(shù)產(chǎn)生的多條堆棧(因?yàn)檫f歸函數(shù)會調(diào)用自己)合并為一條彤灶。
Top Functions:找到最耗時(shí)的函數(shù)或方法。
Allocations
我們經(jīng)常需要從服務(wù)器下載大量圖片批旺,特別是開發(fā)照片類的應(yīng)用枢希。但往往稍不注意,內(nèi)存使用就會暴增朱沃,所以得保證把這些圖片緩存下來以便重復(fù)使用。下面來看看演示程序中內(nèi)存分配的例子茅诱。
從圖中可以看到逗物,每次點(diǎn)擊「Reload」重新載入圖片時(shí),內(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)后黍檩,點(diǎn)「Reload」五次,這回在 Allocations 列表中卻看不到 ImageIO_PNG_Data 對象了始锚,這說明它是空的刽酱,沒有任何圖像數(shù)據(jù)。同時(shí)瞧捌,All Heap Allocations 的大小已從剛才的 14.61 MB 降到了 2.51 MB棵里。Anonymous VM(匿名虛擬內(nèi)存)是系統(tǒng)為程序預(yù)留的、可能會立即被重復(fù)使用的一部分可用內(nèi)存。要防止程序崩潰衍慎,就別讓堆的尺寸增長太快转唉。
還有就是,例子用的是異步方式來加載圖片稳捆,這樣用不著等到所有圖片下載完才能在界面中顯示赠法。大多數(shù)圖像緩存庫都會把加載工作放到后臺,以避免延長主線程的響應(yīng)周期乔夯。
All Heap Allocations 是程序真實(shí)的內(nèi)存分配情況砖织,All Anonymous VM則是系統(tǒng)為程序分配的虛擬內(nèi)存,為的就是當(dāng)程序有需要的時(shí)候末荐,能夠及時(shí)為程序提供足夠的內(nèi)存空間侧纯,而不會現(xiàn)用現(xiàn)創(chuàng)建。
Leaks
盡管 Apple 推出的 ARC 可以有效防范內(nèi)存泄漏甲脏,但出問題的機(jī)率還是會有眶熬,Swift 也不例外。鑒于篇幅有限块请,本文就不涉及內(nèi)存和 ARC 的工作原理了娜氏,具體可以參考官方文檔。我會用代碼來觸發(fā)內(nèi)存泄漏墩新。
首先從最底層上說贸弥,當(dāng)兩個(gè)對象相互建立了強(qiáng)引用(strong reference),當(dāng)一個(gè)對象被釋放海渊,另一個(gè)對象由于是強(qiáng)引用的關(guān)系不允許被釋放绵疲,此時(shí) ARC 無法確定沒被釋放的對象到底還有沒有用,于是就導(dǎo)致了內(nèi)存泄漏臣疑。
要解決這個(gè)問題盔憨,可以將其中的一個(gè)對象中變量設(shè)為 weak,不讓它出現(xiàn)在保留周期中讯沈。很多開發(fā)者在管理 view controller 時(shí)常會在內(nèi)存泄漏上中招般渡,以為換了新的 controller,老的 controller 就被釋放回收了芙盘,其實(shí)還沒驯用。這樣代碼一多,就會造成很多對象都沒被釋放儒老。所以用這個(gè)工具把整個(gè)應(yīng)用跑一遍蝴乔,把那些斷鏈的強(qiáng)引用清理干凈,會大有裨益驮樊。
除了上述這三類工具薇正,Instruments 還有很多實(shí)用的工具片酝,推薦大家根據(jù)自己的關(guān)注點(diǎn),花些時(shí)間去學(xué)學(xué)挖腰。比如:
Core Data:監(jiān)測讀取雕沿、緩存未命中、保存等操作猴仑,能直觀顯示是否保存次數(shù)遠(yuǎn)超實(shí)際需要审轮。
Cocoa Layout:觀察約束變化,找出布局代碼的問題所在辽俗。
Network:跟蹤 TCP / IP和 UDP / IP 連接疾渣。
Automations:創(chuàng)建和編輯測試腳本來自動化 iOS 應(yīng)用的用戶界面測試。
最后小總結(jié)下崖飘。我倒不想一味夸大 Instruments 的作用榴捡,如果應(yīng)用跑得挺痛快,沒出現(xiàn)啥調(diào)皮行為朱浴,大可把它忽略吊圾,等到問題來了再做優(yōu)化。對于新手來說翰蠢,花些時(shí)間了解 Instruments 的功能街夭,多調(diào)試多積累經(jīng)驗(yàn),這樣做出來的應(yīng)用在用戶體驗(yàn)上肯定錯(cuò)不了躏筏。
你最常用的 Instruments 工具都有哪些?歡迎與我們分享呈枉。
原文:How To Use The 3 Instruments You Should Be Using譯者:LeanCloud