iOS 調(diào)試技能 - bug定位、性能調(diào)試、常見問題分析

1. 常用調(diào)試方式

Print VS 單步調(diào)試

說到調(diào)試,剛?cè)腴T編程時,用得最多的無疑是 print,畢竟連教材都是這樣寫的,直接打印,簡單明了。但是當打印內(nèi)容太多時,就容易看得頭暈?zāi)X脹了,這里以 Swift 為例,稍微改進下 print 方法:

/**
print log
#file       String    包含這個符號的文件的路徑
#line       Int       符號出現(xiàn)處的行號
#column     Int       符號出現(xiàn)處的列
#function   String    包含這個符號的方法名字
*/
func printLog<T>(_ message: T,
                    file: String = #file,
                    method: String = #function,
                    line: Int = #line)
{
    #if DEBUG
    print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
    #endif
}

打印的時候輸入文件的路徑,行列號和方法明等,這樣更方便通過 print 的日志來精準定位問題。但是通過 print 來調(diào)試時,有時候就不容易發(fā)現(xiàn)一些邏輯上的問題,或者需要使用大量的 print 輸出日志,這個時候就可以考慮使用單步調(diào)試:

ADD2B91E-EA8A-4C0B-976A-9E9BE78C27E9.png

單步調(diào)試的時候可以逐步查看代碼的執(zhí)行過程,是解決 bug 的神器,如果你是一位新手,這肯定是你首要學(xué)會的技能,在單步調(diào)試的時候,一般都會結(jié)合 LLDB 命令來使用,關(guān)于 LLDB 的內(nèi)容下面會詳細說。

但在實際開發(fā)中,有些場景是無法通過單步調(diào)試來復(fù)現(xiàn)的,比如說多線程的場景下,在使用單步時,很多時候是無法復(fù)現(xiàn)真實場景的,這個時候就需要使用萬能的 print 了??傊瑑煞N方式是必不可少的,在實際開發(fā)中,很多時候我們不會直接使用 print,一般都會使用日志框架,來對日志進行和記錄和收集。

Crash Report

在我們平常的開發(fā)中,你提交測試包給 QA 測試時,他那邊出現(xiàn)在了 crash,但卻不是調(diào)試模式下,這里我們就可以通過通過測試設(shè)備,在 Xcode 的 Devices 中把 crash 日志導(dǎo)出來:

5786A795-FC55-4A6F-8C3D-8CFE014257CC.png

關(guān)于如何去閱讀 Crash report 和定位該 Crash 原因,可以看我之前寫的文章:淺談 Crash Report,這里就不再重復(fù)談。

dSYM 文件

首先來科普下什么是 dSYM 文件:

Xcode編譯項目后,我們會看到一個同名的 dSYM 文件,dSYM 是保存函數(shù)地址映射信息的文件,調(diào)試的 symbols 都會包含在這個文件中,并且每次編譯項目的時候都會生成一個新的 dSYM 文件。

應(yīng)用上架后,可以通過類似友盟統(tǒng)計等工具收集線上的 Crash,這里直接以友盟的為例,先看下 Crash 信息:

601D48A0-954A-4A0B-9AE3-252E7C966999.png

這里可以看出這個錯誤的原因是數(shù)組越界了,那么問題來了,我們并不知道是哪里越界,上面只給出了一個內(nèi)容地址:

5   YHRSS                        0x1000420b0 YHRSS + 270512
6   YHRSS                        0x100041378 YHRSS + 267128

這時就可以通過 dSYM 文件來定位出問題的地方了。首先通過 archives 來找到 dSYM 文件:

步驟1:


步驟1.png

步驟2:


步驟2.png

步驟3:


步驟3.png

我們 cd 到該文件目錄下,然后執(zhí)行:

atos -arch arm64 -o YHRSS 0x1000420b0

注意這里的 -arch 是和上面 crash 報告中的對應(yīng),否則是看不到相應(yīng)的信息的:

$ atos -arch arm64 -o YHRSS 0x1000420b0
specialized YHArticlesViewController.tableView(_:heightForRowAt:) (in YHRSS) (YHArticlesViewController.swift:215)

這樣我們能就精準地獲取 crash 出現(xiàn)的具體位置,然后就該是發(fā)揮自我價值的時候了。

那些項目中遇到的常見問題定位

循環(huán)引用快速定位和解決

如果你懷疑存在循環(huán)引用,你可以 Instrument 工具來定位,但這也太麻煩了,你可以直接在 deinit{} 方法( OC 中對應(yīng)的就是 dealloc 方法)里面打一個斷點,如果頁面退出時沒有執(zhí)行到該處,就說明該頁面存在循環(huán)引用,頁面內(nèi)存沒有辦法釋放。

如果存在循環(huán)引用,那么首先要檢查的是 block 里面的 self 是不是需要 weak,自定義的 delegate 是不是寫成了 strong,絕大部分都是這兩個原因?qū)е碌模饌€去檢查就好。

2. LLDB 常用命令的使用

什么是 LLDB

LLDB 是 Xcode 內(nèi)置的調(diào)試工具,它與 LLVM 編譯器一起,給開發(fā)者提供更豐富的流程控制和數(shù)據(jù)檢測的調(diào)試功能,它的主要功能是為 Xcode 提供底層調(diào)試環(huán)境。

常用命令

  1. help 最牛逼的命令
    help 可以輸出 LLDB 的命令,使用 help <command> 可以輸出相應(yīng)命令的 help。

    圖片.png
  1. po、p 打印值

    圖片.png

    po 和 p 的區(qū)別在于使用po只會輸出對應(yīng)的值,p 則會返回值的類型以及命令結(jié)果的引用名。

  2. exp 輸出或修改值(主要作用是修改值)

    圖片.png
  3. bt 當前線程的調(diào)用堆棧,可能通過后面添加數(shù)字來限制輸出線程數(shù),如 bt 5,只輸出前5個。

    52245C67-3376-4A95-A323-A3875BEBF2F9.png
  4. thread return 跳出當前方法的執(zhí)行(thread return 0 設(shè)置返回值),但在 swift 中,是無法使用的,已知的問題了,只能等待修復(fù)吧,這里給個 OC 的例子:

    51084CDC-031A-490C-A0BE-474DA5C6B423.png

3. Instrument 的使用

寫在最前面,在做性能測試的時候,不能用模擬器,用真機,用真機,用真機,重要的事情說三次。

Time Profiler

time profile 是時間分析工具,主要用來檢測應(yīng)用 CPU 的使用情況,可以看到應(yīng)用程序中各個方法消耗 CPU 時間。關(guān)于概念,這里就不詳細介紹了,直接進行實際操作:

  1. 通過 xcode 中的 product --> profile 來啟動 Instrument,并選擇 Time Profiler 工具:

    BAE9B1B4-19D3-447D-9D59-42955BC22114.png
  2. 運行 Time Profiler,配置顯示方式,分線程顯示和隱藏系統(tǒng)的無關(guān)內(nèi)容:

    B6CA42E2-0AEC-4787-AA39-C1B48FD7CF1F.png
  3. 在手機上執(zhí)行想要測試的操作,執(zhí)行完后停止 Time Profiler 進行分析:

    CBE4F6B3-6DD8-4CC0-8280-8F15A599B425.png
  4. 找到主要耗時的地方,并定位到具體的代碼行(點擊方法的小箭頭就可以進入相應(yīng)的代碼處):

    11C6F965-91F3-4A44-ACF8-1F2FC03F2DC3.png

    這里可以看出,主要有兩一個耗時的操作,但明顯后面那我格式轉(zhuǎn)換我們沒有辦法去處理,我們只能從第一個入手。它每次創(chuàng)建都比較耗時,那么我們就不要多次去創(chuàng)建,因為它每次使用的格式的都是一樣的,這樣我們實質(zhì)上只需要創(chuàng)建一次就可以了。那我們有什么方法去只創(chuàng)建一次呢,首先能想到的肯定是單例,但是用單例太麻煩了,通過 static 定義成一個常量就可以了,就這樣,這處的性能問題就解決了,其它地方也可以通過同樣的方法,逐步分析和解決就可以了。

Leaks

使用的步驟幾乎和上面的一樣,這里就不重復(fù)上圖了,但在出現(xiàn)內(nèi)存泄露的地方,我們需要手動去選取對應(yīng)的位置,這樣才方便分析問題:

3404A8AC-066D-4800-89BE-9DB013D2B2B0.png

因為 Leaks 的使用和 Time Profiler 是一樣的,這里就不去重復(fù)描述使用過程,在這里簡單地介紹下會出現(xiàn)內(nèi)存泄露的常見情況:

  1. 循環(huán)引用
  2. ARC 中使用 C 方式開辟的內(nèi)存沒有手動釋放
  3. URLSession 對象的多次創(chuàng)建,使用 AFNetworking 時也是同理

這里單獨針對 URLSession 對象的多次創(chuàng)建會導(dǎo)致內(nèi)存泄露問題單獨說明下,其實我們只要看過 URLSession 的文檔我們就知道了:

Important

The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until it exits.

session 對象會一直持有強引用,導(dǎo)致無法釋放,多次創(chuàng)建就會有內(nèi)存泄露問題,關(guān)于 session 的使用,我們應(yīng)該使用一個單例來管理。而且唯一的 session 還可以加快網(wǎng)絡(luò)請求,如連接復(fù)用等,這里就不詳細說,回頭有時間再單獨寫一篇相關(guān)的文章,畢竟這不是一兩句話就能說清楚的事情。

參考文獻

  1. 與調(diào)試器共舞 - LLDB 的華爾茲
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容