Instruments Tutorial: Getting Started
學習如何使用工具來捕獲和修復應用程序中的內存問題和性能bug,使它們更快、響應更快。
除了通過添加特性來改進他們的應用程序之外,所有優秀的應用程序開發人員都應該做一件事:檢驗他們的代碼!
本教程將向您展示如何使用隨Xcode附帶的Instruments工具的最重要特性。它允許您檢查代碼的性能問題、內存問題、引用周期和其他問題。
一: 準備開始
在這個Instruments
教程中,您不會從頭開始創建一個應用程序。相反,下載材料為您提供了一個完整的示例項目。您的任務是通過應用程序和改進它使用工具作為您的指導,就像您將為自己的應用程序做!
使用本教程頂部或底部的“下載材料”按鈕下載項目材料。在Xcode中打開啟動項目。
這個示例應用程序使用Flickr API搜索圖像。要使用API,您需要一個API密鑰。對于演示項目,您可以在Flickr的網站上生成一個示例密鑰。只需在Flickr API Explorer中執行任何搜索,并從底部的URL復制API鍵。它遵循“&api_key=”一直到下一個“&”。
Then the API key is: ff417a50b95180cb0c7e3b68a8749fba.
打開FlickrAPI.swift并將現有的API鍵值替換為您的新值。
請注意,API密鑰大約每天都在變化,因此偶爾需要重新生成一個新密鑰。當密鑰無效時,應用程序會提醒你。
建立和運行,執行搜索,點擊結果,你會看到像這樣:
[圖片上傳失敗...(image-56407-1592311788965)]
玩一下這個應用程序,看看它的基本功能。您可能會認為,一旦UI看起來很好,應用程序就準備好提交到商店了。然而,您將看到使用工具可以為您的應用程序增加的價值。
本教程的其余部分將向您展示如何查找和修復應用程序中仍然存在的問題。您將看到工具如何使調試問題變得更加容易!
二: Time for Profiling
[圖片上傳失敗...(image-b3fc72-1592311788965)]
您將看到的第一個工具是時間分析器。每隔一定的時間,儀器就會停止程序的執行,并對每個正在運行的線程進行堆棧跟蹤??梢园阉醋魇窃赬code的調試器中單擊暫停按鈕。
以下是Time Profiler
的截圖:
[圖片上傳失敗...(image-830618-1592311788965)]
這個屏幕顯示了調用樹。調用樹顯示了在應用程序中執行各種方法所花費的時間。每一行都是程序執行路徑所遵循的不同方法。工具通過計算分析器在每種方法中停止的次數來估計在每種方法中花費的時間。
例如,如果以1毫秒的間隔抽取100個樣本,并且在10個樣本中有一個特定的方法出現在堆棧的頂部,那么可以推斷該應用程序在該方法中花費了大約總執行時間的10%(10毫秒)。這是一個相當粗略的近似,但它是有效的!
注意:通常,你應該總是在實際設備上配置你的應用程序,而不是在模擬器上。iOS模擬器擁有你的Mac的所有配置,而設備擁有移動硬件的所有限制。您的應用程序在模擬器中可能運行得很好,但一旦它在實際設備上運行,您可能會發現性能問題。
三: Instrumenting
從Xcode的菜單欄中,選擇產品short - term Profile或按Command-I鍵。這將構建應用程序并啟動儀器。你會看到一個選擇窗口,看起來像這樣:
[圖片上傳失敗...(image-b2bb73-1592311788965)]
這些都是儀器自帶的不同模板。
選擇Time Profiler
,然后單擊選擇
。這會打開一個新的Instruments文檔。點擊左上方的紅色記錄按鈕,開始記錄并啟動app。macOS可能會要求您輸入密碼,授權Instruments
分析其他進程。不要害怕,在這里提供是安全的!
在儀器窗口中,您可以看到時間在增加,屏幕中央的圖形上方有一個小箭頭從左向右移動。這表明應用程序正在運行。
現在,開始使用該應用程序。搜索一些圖片,并深入到一個或多個搜索結果。你可能已經注意到進入搜索結果非常緩慢,滾動搜索結果列表也非常煩人。這是一個非常笨重的應用程序!
你很幸運。你就要著手修理它了!然而,你首先要對你在儀器中看到的東西有一個快速的綱要。
首先,確保工具欄右邊的視圖選擇器同時選擇了兩個選項,像這樣:
[圖片上傳失敗...(image-961c8d-1592311788965)]
這確保了所有面板都是打開的。現在,看看下面的截圖和下面每個部分的解釋:
[圖片上傳失敗...(image-cb594d-1592311788965)]
- 1:
Recording controls
:紅色的record
按鈕停止和啟動當前測試中的應用程序。pause按鈕暫停應用程序的當前執行。 - 2:
Run timer
:定時器計數多長時間的profiled應用程序已經運行,它運行了多少次。點擊停止按鈕,然后重新啟動應用程序。你會看到現在的顯示顯示運行2的2。 - 3:
Instrument track
:在你選擇的時間分析器模板的情況下,只有一個instrument
,所以只有一個痕跡。在本教程的后面部分,您將了解有關該圖形的更多細節 - 4:
Detail panel
:顯示有關您正在使用的特定儀器的主要信息。在本例中,它顯示了“最熱門”的方法——即使用最多CPU時間的方法。
在細節面板的頂部,單擊Profile
并選擇Samples
。在這里,您可以查看每一個樣本。點擊一些示例;您將看到捕獲的堆棧跟蹤顯示在擴展細節檢查器的右側。完成后切換回配置文件。 - 5:
Inspectors panel
:有兩個檢查器—擴展細節和運行信息—您將很快了解到更多。
深入探索
執行圖像搜索,并查看結果。我個人喜歡搜索“狗”,但選擇你想要的-你可能是一個貓寵!
現在,上下滾動列表幾次,這樣您就可以在時間分析器中獲得大量的數據。您應該注意到屏幕中間的數字正在變化,圖形正在填充。這告訴你你的應用程序正在使用CPU周期。
直到它像黃油一樣滾動之前,任何收藏視圖都無法交付!
為了幫助查明問題,您將設置一些選項。單擊“停止”,然后在細節面板下面單擊Call Tree
。在彈出窗口中,選擇Separate by Thread
,Invert Call Tree
和Hide System Libraries
。它會是這樣的:
[圖片上傳失敗...(image-b5bd8-1592311788965)]
下面是每個選項對左邊表格中顯示的數據所做的操作:
-
Separate by State
: 這個選項根據應用程序的生命周期狀態對結果進行分組,是檢查應用程序在什么時候做了多少工作的有用方法。 -
Separate by Thread
: 分別處理每個線程。這使您能夠了解哪些線程對CPU的使用量最大。 -
Invert Call Tree
: 使用此選項,堆棧跟蹤首先顯示最近的幀。 -
Hide System Libraries
: 當你選擇這個選項時,只會顯示來自你自己的應用程序的符號。選擇這個選項通常很有用,因為通常您只關心CPU在您自己的代碼中花費的時間——對于系統庫使用了多少CPU,您無法做太多事情! -
Flatten Recursion
: 此選項顯示調用自己的遞歸函數,每個堆棧跟蹤中只有一個條目,而不是多次。 -
Top Functions
: 啟用這一功能后,工具會將在一個函數中花費的總時間考慮為該函數中直接使用的時間的總和,以及該函數調用的函數所花費的時間。如果函數調用B,那么工具報告的時間所花費的時間在B +所花費的時間,這才會真正有用,因為它允許您選擇最大的時間圖每次陷入調用堆棧中,重點關注在你最耗時的方法
掃描結果,以確定權重列中哪些行的百分比最高。請注意,帶有主線程的行占用了很大一部分CPU周期。通過單擊文本左邊的小箭頭展開這一行,并向下鉆取,直到看到您自己的方法之一,標有“person”符號。雖然有些值可能略有不同,但條目的順序應類似于下表:
[圖片上傳失敗...(image-582c85-1592311788965)]
嗯,這看起來肯定不太好。這款應用程序花了大量時間在對縮略圖應用“色調”濾鏡的方法上。這應該不會讓您感到吃驚,因為表格加載和滾動是UI中最笨重的部分,而這正是表格單元格不斷更新的時候。
要了解該方法的更多內容,請雙擊表中該方法所在的行。這樣做將產生以下觀點:
[圖片上傳失敗...(image-54e5ee-1592311788965)]
applyTonalFilter()
是在擴展中添加到UIImage的一個方法,它在應用圖像過濾器后花費大量時間調用創建CGImage
輸出的方法。
對于加速這個過程,您可以做的不多。創建圖像是一個密集的過程,需要多長時間就花多長時間。嘗試后退一步查看應用程序調用applytonalfilter()
的位置。在代碼視圖頂部的breadcrumb
路徑中單擊Root返回到上一個屏幕:
[圖片上傳失敗...(image-aae3a9-1592311788965)]
現在,單擊表頂部applyTonalFilter
行左邊的小箭頭。這將顯示applyTonalFilter
的調用者——您可能還需要展開下一行。在分析Swift
時,有時會在調用樹中出現以@objc
為前綴的重復行。你感興趣的第一行帶有person
圖標,表示它屬于你的應用程序的目標:
[圖片上傳失敗...(image-1b0e89-1592311788965)]
在本例中,此行引用結果collectionView
的(_:cellForItemAt:)。雙擊該行以查看來自項目的關聯代碼。
現在你可以看到問題是什么了??匆幌碌?0行:應用色調過濾器的方法需要很長時間來執行,并且您直接從collectionView(_:cellForItemAt:)
調用它。這將阻塞主線程,因此每次請求過濾后的圖像時,將阻塞整個UI。
四: 卸貨工作
為了解決這個問題,您將采取兩個步驟:首先,使用DispatchQueue.global().async
將圖像過濾卸載到一個后臺線程。然后,在生成后緩存每個圖像。在starter
項目中包含了一個簡單的圖像緩存類——它有一個引人注目的名稱ImageCache
,它只是將圖像存儲在內存中,并使用給定的鍵檢索它們。
現在,您可以切換到Xcode,手動在Instruments
中找到您要查看的源文件。但是,在Instruments
中有一個方便的打開Xcode按鈕。定位它在面板上面的代碼,并點擊它:
[圖片上傳失敗...(image-cc1-1592311788965)]
可以Xcode在正確的位置打開。
現在,在collectionView(_:cellForItemAt:)
中,用以下代碼替換對loadThumbnail(for:completion:)
的調用:
ImageCache.shared.loadThumbnail(for: flickrPhoto) { result in
switch result {
case .success(let image):
if cell.flickrPhoto == flickrPhoto {
if flickrPhoto.isFavourite {
cell.imageView.image = image
} else {
// 1
if let cachedImage =
ImageCache.shared.image(forKey: "\(flickrPhoto.id)-filtered") {
cell.imageView.image = cachedImage
} else {
// 2
DispatchQueue.global().async {
if let filteredImage = image.applyTonalFilter() {
ImageCache.shared.set(filteredImage,
forKey: "\(flickrPhoto.id)-filtered")
DispatchQueue.main.async {
cell.imageView.image = filteredImage
}
}
}
}
}
}
case .failure(let error):
print("Error: \(error)")
}
}
這段代碼的第一部分與之前一樣,從web
加載Flickr
照片的縮略圖。如果照片是收藏,單元格將不加修改地顯示縮略圖。但是,如果照片不是最喜歡的,它會應用色調濾鏡。
這就是你改變的地方:
- 檢查此照片的已過濾圖像是否存在于圖像緩存中。如果是的那就顯示圖像。
- 如果不是,則分派調用將色調過濾器應用于后臺隊列。這允許UI在過濾器運行時保持響應。篩選完成后,將圖像保存在緩存中并更新主隊列上的圖像視圖。
這是過濾后的圖像,但仍有原始Flickr
縮略圖需要解決。打開Cache.swift
并找到loadThumbnail(for:completion:)
。將其改為:
func loadThumbnail(for photo: FlickrPhoto,
completion: @escaping FlickrAPI.FetchImageCompletion) {
if let image = ImageCache.shared.image(forKey: photo.id) {
completion(Result.success(image))
} else {
FlickrAPI.loadImage(for: photo, withSize: "m") { result in
if case .success(let image) = result {
ImageCache.shared.set(image, forKey: photo.id)
}
completion(result)
}
}
}
這與處理過濾后的圖像非常相似。如果緩存中已經存在一個映像,那么可以直接用緩存的映像調用完成閉包。否則,從Flickr
加載圖像并將其存儲在緩存中。
按Command-I
再次在儀器中運行應用程序。注意,這次Xcode并沒有要求您使用哪個工具。這是因為您的應用程序仍然有一個打開的窗口,而且Instruments
假設您希望使用相同的選項再次運行。
執行更多的搜索。UI現在不那么笨重了!該應用程序現在在背景中應用圖像過濾器并緩存結果,所以圖像只需要過濾一次。您將在調用樹中看到許多dispatch_worker_threads
。這些都是處理重型應用圖像過濾器。
看起來太棒了!是時候出貨了嗎?沒有!
五: Allocations, Allocations and Allocations
那么你接下來要追蹤什么bug呢?
這個項目中隱藏著一些你可能不知道的東西。您可能聽說過內存泄漏。但你可能不知道的是,實際上有兩種泄漏:
- 真正的內存泄漏:
當對象不再被任何對象引用,但仍被分配時發生。這意味著內存永遠不能被重用。
即使使用Swift和ARC幫助管理內存,最常見的內存泄漏類型還是保留循環或強引用循環。當兩個對象保持對彼此的強引用,以便每個對象避免釋放另一個對象時,就會發生這種情況。因此,他們的記憶永遠不會被釋放。 - 無限內存增長:
當內存被連續分配并且沒有機會被釋放時發生。如果繼續不進行檢查,您將耗盡內存。在iOS上,這意味著系統會終止你的應用。
現在你們要研究分配工具。這個工具提供了你的應用程序創建的所有對象的詳細信息,以及支持它們的內存。它還顯示您保留了每個對象的計數。
六: Instrumenting Allocations
要重新創建新的instruments配置文件,請退出instruments應用程序。不要擔心保存這次運行?,F在,在Xcode中按Command-I鍵,從列表中選擇Allocations
,然后單擊Choose。
[圖片上傳失敗...(image-ddaf9f-1592311788965)]
過一會兒,你會看到分配工具。它看起來應該很熟悉,因為它看起來很像Time Profiler.
[圖片上傳失敗...(image-f40fa2-1592311788965)]
點擊左上角的Record
按鈕來運行應用程序。這次你會注意到兩條音軌。出于本教程的目的,您將只關心稱為All Heap
和 Anonymous VM
的方法。
[圖片上傳失敗...(image-88ee59-1592311788965)]
通過在應用程序上運行分配工具,在應用程序中進行五次不同的搜索,但還沒有深入到結果。確保搜索有一些結果?,F在,讓應用程序稍等幾秒鐘。
[圖片上傳失敗...(image-3f85bc-1592311788965)]
您應該注意到All Heap
和 Anonymous VM
跟蹤中的圖形一直在上升。這告訴你你的應用程序正在分配內存。正是這個特性將指導您尋找無限的內存增長。
七: Generation Analysis
你要做的是生成分析。為此,單擊細節面板底部標記生成按鈕:
[圖片上傳失敗...(image-5754df-1592311788965)]
點擊它,你會看到一個紅旗出現在軌道上,像這樣:
[圖片上傳失敗...(image-84dcd4-1592311788965)]
生成分析的目的是多次執行一個操作,看看內存是否以無限制的方式增長。打開搜索結果,等待幾秒鐘圖像加載,然后返回主頁。再次為這一代人做標記。對于不同的搜索重復這樣做。
在檢查了幾次搜索后,instruments
看起來是這樣的:
[圖片上傳失敗...(image-a23acb-1592311788965)]
在這一點上,你應該開始懷疑了。請注意,隨著每次深入搜索,藍色圖是如何上升的。這當然不好。但是,等等,內存警告呢?
內存警告是iOS告訴應用程序內存部門的情況越來越緊張,需要清空一些內存的一種方式。
這種增長可能不僅僅是由于你的應用。它可能是在UIKit深處的一些東西抓住了內存。在對系統框架和應用程序進行攻擊之前,先給它們一個清除內存的機會。
模擬記憶警告,在儀器菜單欄中選擇Instrument ? Simulate Memory Warning
,或在Hardware ? Simulate Memory Warning
模擬記憶警告。您會注意到內存使用有所下降,或者根本沒有下降。當然不會回到它應有的位置。所以在某些地方仍然存在無限的記憶增長。
您在檢查搜索的每次迭代之后標記了代,這樣您就可以看到在每代之間分配了哪些內存。查看細節面板,您會看到很多代。
在每一代中,您將看到在標記該代時已分配并仍然駐留的所有對象。后續代將只包含上一代標記后的對象。
看看生長柱,你會發現在某個地方確實有生長。打開其中一個世代,你會看到
[圖片上傳失敗...(image-b3f13f-1592311788965)]