Xcode Instruments除錯swift入門教程

敲鐘人Quasimodo發表於2018-12-18

無論您是在許多iOS應用程式上工作,還是仍在開始使用第一個應用程式:您無疑會想出新功能,並且想知道您可以做些什麼來使您的應用程式更加出色

除了通過新增功能改進您的應用程式之外,所有優秀的應用程式開發人員都應該做的一件事就是......檢測他們的程式碼

本教程將向您展示如何使用Xcode附帶的名為Instruments的工具的最重要功能。它允許您檢查程式碼中的效能問題,記憶體問題,迴圈引用和其他問題。

在本教程中,您將學習:

  • 如何使用Time Profiler工具確定程式碼中的熱點,以便提高程式碼的效率,以及
  • 如何使用Allocations工具和Visual Memory Debugger檢測和修復程式碼中強引用週期等記憶體管理問題。

注意:本教程假設您熟悉Swift和iOS程式設計。如果您是iOS程式設計的完全初學者,您可能希望檢視本網站上的其他一些教程。本教程使用故事板,因此請確保您熟悉該概念;一個好的起點是本網站上的教程。

搞定?準備好潛入迷人的Instuments世界! :]

開始

對於本教程,您將不會從頭開始建立應用程式;相反,已經為您提供了一個示例專案。您的任務是通過應用程式並使用Instruments作為指南進行改進 - 與您優化自己的應用程式非常相似!

下載入門專案然後解壓縮並在Xcode中開啟它。

此示例應用程式使用FlickrAPI搜尋影象。要使用API,您需要一個API金鑰。對於演示專案,您可以在Flickr的網站上生成示例金鑰。進入http://www.flickr.com/services/api/explore/?method=flickr.photos.search然後拷貝API key從這個url的底部,在“&api_key=”的後邊。

舉個例子,如果URL是http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=6593783 efea8e7f6dfc6b70bc03d2afb&format=rest&api_sig=f24f4e98063a9b8ecc8b522b238d5e2f,API key就是:6593783efea8e7f6dfc6b70bc03d2afb。

將其貼上到FlickrAPI.swift的頂部,替換現有的API金鑰。

請注意,此示例API金鑰每天都會更改,因此您偶爾必須重新生成新金鑰。只要金鑰不再有效,應用程式就會提醒您。

構建並執行應用程式,執行搜尋,單擊結果,您將看到如下內容:

Xcode Instruments除錯swift入門教程
瀏覽應用程式並檢視基本功能。您可能會想到,一旦UI看起來很棒,應用程式就可以提交商店了。但是,您將看到使用Instruments可以新增到您的應用程式的價值。

本教程的其餘部分將向您展示如何查詢和修復應用程式中仍存在的問題。您將看到Instruments如何使除錯問題變得更加容易! :]

Time Profiler

Xcode Instruments除錯swift入門教程

您將看到的第一個工具是Time Profiler。在測量的時間間隔內,Instruments將停止程式的執行並在每個執行的執行緒上執行堆疊跟蹤。可以將其視為單擊Xcode偵錯程式中的暫停按鈕。

以下是Time Profiler的預覽:

Xcode Instruments除錯swift入門教程

此螢幕顯示Call Tree。CallTree顯示在應用程式中的各種方法中執行所花費的時間。每行都是程式執行路徑所遵循的不同方法。在每種方法中花費的時間可以根據每種方法中分析器停止的次數來確定。

例如,如果以1毫秒的間隔完成100個樣本,並且發現特定方法位於10個樣本中的堆疊頂部,那麼您可以推斷出總執行時間的大約10% - 10毫秒 - 花費了在那種方法中。這是一個相當粗略的近似,但它又不是不能用!

注意:通常,您應始終在實際裝置上而不是模擬器上分析您的應用。 iOS模擬器具有Mac背後的所有功能,而裝置將具有移動硬體的所有限制。您的應用程式似乎在模擬器中執行得很好,但是一旦它在真實裝置上執行,您可能會發現效能問題。 此外,Xcode 9測試版和使用Instruments的模擬器存在一些問題。

所以沒有任何進一步的麻煩,是時候去使用Time Profiler了!

從Xcode的選單欄中,選擇Product \ Profile,或按⌘I。這將構建應用程式並啟動Instrument。您將看到一個如下所示的選擇視窗:

Xcode Instruments除錯swift入門教程
這些都是Instrument附帶的不同模板。

選擇Time Profiler,然後單擊“選擇”。這將開啟一個新的Instruments文件。單擊左上角的紅色記錄按鈕開始錄製並啟動應用程式。可能會要求您輸入密碼以授權儀器分析其他過程 - 不要擔心,這裡提供是安全的! :]

在視窗中,您可以看到計時的時間,以及在螢幕中心的圖形上方從左向右移動的小箭頭。這表明該應用正在執行。

現在,開始使用該應用程式。搜尋一些影象,並深入檢視一個或多個搜尋結果。您可能已經注意到,進入搜尋結果的速度非常慢,滾動瀏覽搜尋結果列表也非常煩人 - 這是一個非常笨重的應用程式!

嗯,你很幸運,因為你即將開始修復它!但是,您首先要快速瞭解您在Instruments中所看到的內容。

首先,確保工具欄右側的檢視選擇器同時選擇了兩個選項,如下所示:

Xcode Instruments除錯swift入門教程
這將確保所有皮膚都是開啟的。現在研究下面的截圖以及它下面每個部分的解釋:
Xcode Instruments除錯swift入門教程

  1. 這些是錄音控制元件。點選紅色的“記錄”按鈕將停止或者啟動當前正在分析的應用程式(它在記錄和停止圖示之間切換)。暫停按鈕完全符合您的預期,並暫停當前應用程式的執行。
  2. 這是執行計時器。計時器計算正在執行的應用程式執行的時間長度以及執行的次數。單擊停止按鈕,然後重新啟動應用程式,您將看到螢幕現在顯示Run 2 of 2。
  3. 這被稱為軌道。對於您選擇的Time Profiler模板,只有一個儀器,因此只有一個軌道。您將在本教程後面的內容中詳細瞭解該圖的具體細節。
  4. 這是細節皮膚。它顯示了您正在使用的特定儀器的主要資訊。在這種情況下,它顯示的是“最熱門”的方法 - 也就是那些耗盡了大部分CPU時間的方法。

單擊“Profile”一詞上此區域頂部的欄,然後選擇“Sample”。在這裡,您可以檢視每個樣本。單擊幾個樣本,您將看到捕獲的堆疊跟蹤顯示在右側的“擴充套件詳細資訊”檢查器中。完成後切換回Profile。 5. 這是檢查皮膚。有兩個檢查項:擴充套件詳細資訊和執行資訊。您很快就會了解有關這些選項的更多資訊。

深入

執行影象搜尋,並深入檢視結果。我個人喜歡搜尋“狗”,但選擇你想要的任何東西 - 你可能是那些愛貓人士之一! :]

現在,在列表中向上和向下滾動幾次,以便在Time Profiler中獲得大量資料。您應該注意到螢幕中間的數字正在變化並且圖形填滿;這告訴您正在使用CPU週期。

沒有桌面檢視可以運送,直到它像黃油一樣滾動!

為了幫助查明問題,您需要設定一些選項。單擊“停止”按鈕,然後在詳細資訊皮膚下單擊“Call Tree”按鈕。在出現的彈出視窗中,選擇“按執行緒分隔”,“反轉呼叫樹”和“隱藏系統庫”。它看起來像這樣:

Xcode Instruments除錯swift入門教程
以下是每個選項對左側表格中顯示的資料的作用:

  • Seprate by State:此選項按應用程式的生命週期狀態對結果進行分組,這是檢查應用程式正在執行的工作量和時間的有用方法。
  • Seprate by Thread:每個執行緒都應該單獨考慮。這使您可以瞭解哪些執行緒負責最大量的CPU使用。
  • Invert Call Tree:使用此選項,堆疊跟蹤將被視為從最遠到最近。
  • Hide System Libraries:選擇此選項後,僅顯示您自己的應用程式中的符號。選擇此選項通常很有用,因為通常只關心CPU在您自己的程式碼中花費時間的位置 - 您無法對系統庫使用的CPU量做多少工作!
  • Flatten Recursion:此選項將遞迴函式(自稱為自身的函式)視為每個堆疊跟蹤中的一個條目,而不是多個。
  • Top Function:啟用此功能會使Instruments將在函式中花費的總時間視為該函式內直接時間的總和,以及該函式呼叫的函式所花費的時間。因此,如果函式A呼叫B,則A的時間被報告為在A PLUS中花費的時間在B中花費的時間。這可能非常有用,因為它允許您在每次下降到呼叫堆疊時選擇最大的時間數字,歸零在你最耗時的方法。

掃描結果以確定“Weight”列中哪些行具有最高百分比。請注意,具有主執行緒的行佔用了相當大比例的CPU週期。通過單擊文字左側的小箭頭展開此行,然後向下鑽取,直到您看到自己的方法之一(標有“人物”符號)。雖然某些值可能略有不同,但條目的順序應與下表類似:

Xcode Instruments除錯swift入門教程
嗯,這當然看起來不太好。絕大部分時間都用在將“色調”濾鏡應用於縮圖照片的方法中。這不應該對你造成太大的衝擊,因為表格載入和滾動是UI中最笨重的部分,而且當表格單元格不斷更新時。

要了解有關該方法中發生的更多資訊,請雙擊表中的行。這樣做會顯示以下檢視:

Xcode Instruments除錯swift入門教程

那很有意思,不是嗎! applyTonalFilter()是一個在擴充套件中新增到UIImage的方法,並且,在應用影象過濾器之後,花費了大量時間來呼叫建立CGImage輸出的方法。

沒有太多可以做的事情來加快速度:建立影象是一個非常密集的過程,並且需要花費很長時間。讓我們試著退後一步,看看呼叫applyTonalFilter()的位置。單擊程式碼檢視頂部的痕跡導航路徑中的Root以返回上一個螢幕:

Xcode Instruments除錯swift入門教程
現在單擊表頂部applyTonalFilter行左側的小箭頭。這將顯示applyTonalFilter的呼叫者。您可能還需要展開下一行;在分析Swift時,呼叫樹中有時會出現重複的行,字首為@objc。您對以“person”符號為字首的第一行感興趣,該符號表示它屬於您應用的目標:

在這種情況下,此行引用結果集合檢視(_:cellForItemAt :)。雙擊該行以檢視專案中的關聯程式碼。

現在您可以看到問題所在。看看第74行;應用色調過濾器的方法需要很長時間才能執行,並且它直接從collectionView(_:cellForItemAt :)呼叫,每當它請求過濾後的影象時,它將阻塞主執行緒(以及整個UI)。

解除安裝工作

要解決這個問題,您需要執行兩個步驟:首先,使用DispatchQueue.global().async將影象過濾解除安裝到後臺執行緒上;然後在每個影象生成後對其進行快取。初學者專案中包含一個簡單的小型影象快取類(引人注目的名稱為ImageCache),它只是將影象儲存在記憶體中並使用給定的金鑰檢索它們。

您現在可以切換到Xcode並手動查詢您在Instruments中檢視的原始檔,但是在您的眼前,有一個方便的Open in Xcode按鈕。在程式碼上方的皮膚中找到它並單擊它:

Xcode Instruments除錯swift入門教程
我去! Xcode在恰當的位置開啟。Boom!

現在,在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 {
          if let cachedImage = ImageCache.shared.image(forKey: "\(flickrPhoto.id)-filtered") {
            cell.imageView.image = cachedImage
           }
           else {
             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載入影象並將其儲存在快取中。

通過導航到Product \ Profile(或⌘I - 重新執行Instruments中的應用程式 - 請記住,這些快捷方式將為您節省一些時間)。

請注意,這次Xcode不會詢問您使用哪種儀器。這是因為您仍然為此應用程式開啟了一個視窗,而Instruments假定您希望使用相同的選項再次執行。

再執行一些搜尋,注意這次UI不是那麼笨重!影象過濾器現在非同步應用,影象在後臺快取,因此一旦只需要過濾一次。您將在呼叫樹中看到許多dispatch_worker_threads - 這些正在處理應用影象過濾器的繁重工作。

看起來很棒!是時候發貨了嗎?還沒! :]

Allocations, Allocations, Allocations

那你接下來要追查什麼錯誤? :]

專案中隱藏著一些您可能不知道的東西。你可能聽說過記憶體洩漏。但你可能不知道的是,實際上有兩種洩漏:

  1. 真正的記憶體洩漏是一個物件不再被任何東西引用但仍被分配的東西 - 這意味著永遠不能重用記憶體。 即使使用Swift和ARC幫助管理記憶體,最常見的記憶體洩漏型別是迴圈持有或迴圈強引用。這是當兩個物件彼此持有強引用時,每個物件使另一個物件不被釋放。這意味著他們的記憶永遠不會被釋放。
  2. 無限的記憶體增長是繼續分配記憶體並且永遠不會被釋放的機會。如果這種情況持續下去,那麼在某些時候系統的記憶體將被填滿,你的手上會有很大的記憶體問題。在iOS上,這意味著該應用程式將被系統殺死。

本教程中涉及的下一個工具是Allocations工具。這將為您提供有關正在建立的所有物件以及支援它們的記憶體的詳細資訊。它還顯示您保留每個物件的計數。

要重新開始使用新儀器配置檔案,請退出Instruments應用程式,不要擔心儲存此特定執行。現在按⌘I,從列表中選擇Allocations儀器,然後單擊Choose。

Xcode Instruments除錯swift入門教程
現在,您應該看到Allocations工具。它應該看起來很熟悉,因為它看起來很像Time Profiler。
Xcode Instruments除錯swift入門教程
單擊左上角的“錄製”按鈕以執行該應用程式。這次你會注意到兩個軌道。出於本教程的目的,您將只關注名為All Heap和Anonymous VM的那個。
Xcode Instruments除錯swift入門教程
在應用程式上執行Allocations工具後,在應用程式中進行五次不同的搜尋,但不會深入檢視結果。確保搜尋有一些結果。現在讓應用程式等待幾秒鐘。
Xcode Instruments除錯swift入門教程
您應該已經注意到All Heap和Anonymous VM軌道中的圖形一直在上升。這告訴你正在分配記憶體。正是這個功能將引導您找到無限的記憶體增長。

您將要執行的是“生成分析”。為此,請單擊名為Mark Generation的按鈕。您可以在詳細資訊皮膚底部找到按鈕:

Xcode Instruments除錯swift入門教程
單擊它,您將看到軌道中出現一個紅色標記,如下所示:
Xcode Instruments除錯swift入門教程
生成分析的目的是多次執行操作,並檢視記憶體是否以無限制的方式增長。深入搜尋,等待幾秒鐘以載入影象,然後返回主頁面。然後再次標記一代。對不同的搜尋重複執行此操作。

經過幾次搜尋後,儀器將如下所示:

Xcode Instruments除錯swift入門教程

此時,你應該開始懷疑。請注意您鑽取的每個搜尋的藍色圖表是如何上升的。嗯,那當然不好。但等等,記憶體警告怎麼樣?你瞭解那些,對嗎?記憶體警告是iOS告訴應用程式記憶體部門事情變得緊張的方式,你需要清除一些記憶體。

這種增長可能不僅僅是因為你的應用程式;它可能是UIKit深處持有記憶體的東西。在指向任何一個之前,先給系統框架和你的應用程式一個清除記憶體的機會。

通過選擇儀器選單欄中的儀器\模擬記憶體警告或模擬器選單欄中的硬體\模擬記憶體警告來模擬記憶體警告。您會注意到記憶體使用量略有下降,或者根本沒有下降。當然不會回到它應該的位置。所以在某個地方仍然存在無限的記憶體增長。

Instruments: 談論我的Generation

在每次迭代鑽取到搜尋之後標記生成的原因是您可以看到在每一個generation之間分配了哪些記憶體。看看細節皮膚,你會看到好幾個generation。

在每個generation中,您將看到所有已分配的物件,並且在生成標記時仍然駐留。之後的generation將僅包含自上一generation標記後的物件。

看看增長列,你會發現某處確實存在增長。開啟其中一代,你會看到:

Xcode Instruments除錯swift入門教程
哇,那有很多物件!你該從哪裡開始呢?

簡單。單擊“增長”標題按大小排序,確保最重的物件位於頂部。在每一代的頂部附近,您會注意到一行標記為ImageIO_jpeg_Data,這聽起來像您應用中處理的內容。單擊ImageIO_jpeg_Data左側的箭頭以顯示與此專案關聯的記憶體地址。選擇第一個記憶體地址以在右側皮膚的“擴充套件詳細資訊”檢查器中顯示關聯的堆疊跟蹤:

Xcode Instruments除錯swift入門教程
此堆疊跟蹤顯示建立此特定物件的時間點。灰色的堆疊跟蹤部分位於系統庫中;黑色部分在您的應用程式程式碼中。嗯,看起來很熟悉:一些黑色條目顯示你的老朋友collectionView(_:cellForItemAt :)。雙擊任何這些條目,Instruments將在其上下文中顯示程式碼。

看看這個方法,你會看到第81行呼叫set(_:forKey :)。請記住,這個方法會快取一個影象,以防以後在應用程式中再次使用它。啊!那肯定聽起來可能是個問題! :]

再次單擊“在Xcode中開啟”按鈕以跳回Xcode。開啟Cache.swift並看一下set(_:forKey :)的實現:

func set(_ image: UIImage, forKey key: String) {
  images[key] = image
}
複製程式碼

這會將影象新增到字典中,該字典鍵入Flickr照片的照片ID。但是如果你檢視程式碼,你會發現影象永遠不會從該字典中清除掉!

這就是你的無限記憶體增長來自:一切都在執行,但應用程式永遠不會從快取中刪除東西 - 它只會新增它們!

要解決此問題,您需要做的就是讓ImageCache監聽UIApplication觸發的記憶體警告通知。當ImageCache收到此訊息時,它必須是一個好公民並清除其快取。

要使ImageCache監聽通知,請開啟Cache.swift並將以下初始化程式和解除初始化程式新增到該類:

init() {
   NotificationCenter.default.addObserver(forName: Notification.Name.UIApplicationDidReceiveMemoryWarning, object: nil, queue: .main) { [weak self] notification in
    self?.images.removeAll(keepingCapacity: false)
  }
}
  
deinit {
  NotificationCenter.default.removeObserver(self)
}
複製程式碼

這會註冊UIApplicationDidReceiveMemoryWarningNotification的觀察者來執行上面的閉包,從而清除影象。

程式碼需要做的就是刪除快取中的所有物件。這將確保不再有任何東西保留在影象上,它們將被解除分配。

要測試此修復程式,請再次啟動儀器(從Xcode使用⌘I)並重復之前執行的步驟。不要忘記最後模擬記憶體警告。

注意:確保從Xcode啟動,觸發構建,而不是僅僅點選Instruments中的紅色按鈕,以確保您使用的是最新程式碼。您可能還希望在分析之前首先構建並執行,因為有時Xcode似乎不會將模擬器中應用程式的構建更新為最新版本(如果您只是Profile)。

這次生成分析應如下所示:

Xcode Instruments除錯swift入門教程
您會注意到記憶體警告後記憶體使用率下降。總體上仍有一些記憶體增長,但遠不及以前那麼多。

之所以還有一些增長的原因,實際上是由於系統庫,你可以做的並不多。似乎系統庫沒有釋放所有記憶體,這可能是設計上的,也可能是一個bug。您在應用程式中所能做的就是釋放盡可能多的記憶體,而您已經完成了! :]

做得好!修補了另外一個問題!現在必須是出貨的時候了!哦,等等 - 你還沒有解決第一種洩漏問題。

強引用迴圈

如前所述,當兩個物件彼此保持強引用時會發生強引用迴圈,因此永遠不能釋放記憶體。您可以使用Allocations儀器以不同的方式檢測這些迴圈。

關閉儀器並返回Xcode。再次選擇Product \ Profile,然後選擇Allocations模板。

Xcode Instruments除錯swift入門教程
這一次,您將不會使用生成分析。相反,您將檢視記憶體中不同型別的物件數量。單擊“錄製”按鈕開始此執行。您應該已經看到大量的物件填滿了細節皮膚 - 太多無法看清楚!要縮小到感興趣的物件,請在左下角的欄位中鍵入“Instruments”作為過濾器。
Xcode Instruments除錯swift入門教程
儀器中值得注意的兩個欄目是#Persistent和#Transient。 Persistent列保留記憶體中當前存在的每種型別的物件數的計數。 “Transient”列顯示已存在但已取消分配的物件數。持久物件正在耗盡記憶體,瞬態物件已釋放記憶體。

您應該看到有一個ViewController的持久例項 - 這是有道理的,因為那是您當前正在檢視的螢幕。還有應用程式AppDelegate的一個例項。

回到應用程式!執行搜尋並深入檢視結果。請注意,現在儀器中出現了一堆額外的物件:SearchResultsViewController和ImageCache等。 ViewController例項仍然是持久的,因為它的導航控制器需要它。沒關係。

現在點按應用中的後退按鈕。 SearchResultsViewController現已從導航堆疊彈出,因此應該取消分配。但它仍然在分配總結中顯示#引用數為1!它為什麼還在那裡?

嘗試執行另外兩次搜尋,然後在每次搜尋後點選後退按鈕。現在有3個SearchResultsViewControllers ?!這些檢視控制器在記憶體中閒置的事實意味著某些內容正在強烈引用它們。看起來你有一個嚴重的迴圈引用!

Xcode Instruments除錯swift入門教程
您在這種情況下的主要線索是,不僅SearchResultsViewController持久存在,而且所有SearchResultsCollectionViewCells也是如此。迴圈引用可能在這兩個類之間。

值得慶幸的是,Xcode 8中引入的Visual Memory Debugger是一個簡潔的工具,可以幫助您進一步診斷記憶體洩漏和迴圈引用。 Visual Memory Debugger不是Xcode儀器套件的一部分,但它仍然是一個非常有用的工具,值得在本教程中包含。來自Allocations儀器和Visual Memory Debugger的交叉引用見解是一種強大的技術,可以使您的除錯工作流程更加有效。

獲取檢視化

退出Allocations儀器並退出儀器套件。

在啟動Visual Memory Debugger之前,在Xcode方案編輯器中啟用Malloc Stack日誌記錄,如下所示:單擊視窗頂部的Instruments Tutorial方案(停止按鈕旁邊),然後選擇Edit Scheme。在出現的彈出視窗中,單擊“執行”部分,然後切換到“診斷”選項卡。選中顯示Malloc Stack的框,然後選擇僅限實時分配選項,然後單擊關閉。

直接從Xcode啟動應用程式。像以前一樣,執行至少3次搜尋以累積一些資料。

現在啟用Visual Memory Debugger,如下所示:

Xcode Instruments除錯swift入門教程

  1. 切換到Debug導航器。
  2. 單擊此圖示,然後從彈出視窗中選擇“View Memory Graph Hierarchy”。
  3. 單擊SearchResultsCollectionViewCell的條目。
  4. 您可以單擊圖形上的任何物件以檢視檢查器窗格中的詳細資訊。
  5. 您可以檢視此區域的詳細資訊。切換到記憶體檢查器。

Visual Memory Debugger暫停您的應用程式並顯示記憶體中物件的視覺化表示以及它們之間的引用。

如上面的螢幕截圖所示,Visual Memory Debugger顯示以下資訊:

  • 堆內容(除錯導航器皮膚):顯示應用程式暫停時在記憶體中分配的所有型別和例項的列表。單擊某個型別會展開該行,以顯示記憶體中型別的單獨例項。
  • 記憶體圖(主皮膚):主視窗顯示記憶體中物件的直觀表示。物件之間的箭頭表示它們之間的引用(強關係和弱關係)。
  • 記憶體檢查器(“實用程式”皮膚):這包括類名稱和層次結構等詳細資訊,以及引用是強還是弱。

請注意Debug導航器中的某些行如何在它們旁邊加上括號括起來的數字。括號中的數字表示該特定型別的例項在記憶體中的數量。在上面的螢幕截圖中,您可以看到,經過少量搜尋後,Visual Memory Debugger會確認您在Allocations工具中看到的結果,即從20到(如果您滾動到搜尋結果的末尾)60個SearchResultsCollectionViewCell例項每個SearchResultsViewController例項都保留在記憶體中。

使用行左側的箭頭展開型別並在記憶體中顯示每個SearchResultsViewController例項。單擊單個例項將在主視窗中顯示該例項及其對它的任何引用。

Xcode Instruments除錯swift入門教程
注意指向SearchResultsViewController例項的箭頭。看起來有一些Swift閉包上下文例項引用了同一個檢視控制器例項。看起來有點懷疑,不是嗎?讓我們仔細看看。選擇其中一個箭頭以在“實用工具”窗格中顯示有關其中一個閉包例項與SearchResultsViewController之間的引用的更多資訊。
Xcode Instruments除錯swift入門教程
在Memory Inspector中,您可以看到Swift閉包上下文和SearchResultsViewController之間的引用是強引用。如果選擇SearchResultsCollectionViewCell和Swift閉包上下文之間的引用,您將看到它也標記為強引用。您還可以看到閉包的名稱是“heartToggleHandler”.A-ha!這是在SearchResultsCollectionViewCell類中宣告的!

在主視窗中選擇SearchResultsCollectionViewCell的例項,以顯示有關檢查器窗格的更多資訊。

Xcode Instruments除錯swift入門教程

在回溯中,您可以看到單元例項已在collectionView(_:cellForItemAt :)中初始化。當您將滑鼠懸停在回溯中的此行上時,會出現一個小箭頭。單擊箭頭將轉到Xcode程式碼編輯器中的此方法。 真棒!

在collectionView(_:cellForItemAt :)中,找到每個單元格的heartToggleHandler變數的設定位置。您將看到以下程式碼行:

cell.heartToggleHandler = { isStarred in
  self.collectionView.reloadItems(at: [indexPath])
}
複製程式碼

當點選集合檢視單元格中的心形按鈕時,此閉包處理。這是強引用迴圈所在,但除非你之前遇到過,否則很難發現。但是由於Visual Memory Debugger,您可以跟蹤到這段程式碼的所有路徑!

閉包Cell使用self引用了SearchResultsViewController,它建立了一個強引用。閉包持有了Self。 Swift實際上強迫你在閉包中明確使用self這個詞(而你通常可以在引用當前物件的方法和屬性時刪除它)。這有助於您更加了解持有它的事實。 SearchResultsViewController還通過其集合檢視對單元格進行了強引用。

要打破強引用迴圈,可以將捕獲列表定義為閉包定義的一部分。捕獲列表可用於將閉包捕獲的例項宣告為弱引用或無主引用:

  • 當捕獲的參考可能在將來變為零時,應該使用Weak。如果它們引用的物件被釋放,則引用變為零。因此,它們是可選型別。
  • 當閉包及其引用的物件將始終具有相同的生命週期並且將同時取消分配時,應使用Unowned。無主引用永遠不會置為nil。

要修復此強引用週期,請將捕獲列表新增到heartToggleHandler,如下所示:

cell.heartToggleHandler = { [weak self] isStarred in
  self?.collectionView.reloadItems(at: [indexPath])
}
複製程式碼

將self宣告為Weak表示即使集合檢視單元格對其進行引用也可以釋放SearchResultsViewController,因為它們現在只是弱引用。釋放SearchResultsViewController將取消分配其集合檢視,進而取消分配單元格。

在Xcode中,再次使用⌘+ I在Instruments中構建和執行應用程式。

使用Allocations儀器再次在Instruments中檢視應用程式(請記住過濾結果以僅顯示作為初始專案一部分的類)。執行搜尋,導航到結果,然後再返回。您應該看到,當您向後導航時,SearchResultsViewController及其單元格現在已被釋放。它們顯示瞬態例項,但沒有持久例項。

迴圈被打破了,提交它吧!!

接下來該做什麼?

這是專案的最終優化版本的下載連結,這完全歸功於Instruments。

既然您已經掌握了本教程中的知識,那就去測試自己的程式碼,看看有什麼有趣的東西出現了!此外,嘗試使儀器成為您通常的開發工作流程的一部分。

您應該經常通過Instruments執行程式碼,並在釋出之前對應用程式進行全面掃描,以確保儘可能多地捕獲記憶體管理和效能問題。

現在去製作一些非常棒且高效的應用! :]

PS:

最近加了一些iOS開發相關的QQ群和微信群,但是感覺都比較水,裡面對於技術的討論比較少,所以自己建了一個iOS開發進階討論群,歡迎對技術有熱情的同學掃碼加入,加入以後你可以得到:

1.技術方案的討論,會有在大廠工作的高階開發工程師儘可能抽出時間給大家解答問題

2.每週定期會寫一些文章,並且轉發到群裡,大家一起討論,也鼓勵加入的同學積極得寫技術文章,提升自己的技術

3.如果有想進大廠的同學,裡面的高階開發工程師也可以給大家內推,並且針對性得給出一些面試建議

Xcode Instruments除錯swift入門教程

相關文章