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)試:

單步調(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)出來:

關(guān)于如何去閱讀 Crash report 和定位該 Crash 原因,可以看我之前寫的文章:淺談 Crash Report,這里就不再重復(fù)談。
dSYM 文件
首先來科普下什么是 dSYM 文件:
Xcode編譯項目后,我們會看到一個同名的 dSYM 文件,dSYM 是保存函數(shù)地址映射信息的文件,調(diào)試的 symbols 都會包含在這個文件中,并且每次編譯項目的時候都會生成一個新的 dSYM 文件。
應(yīng)用上架后,可以通過類似友盟統(tǒng)計等工具收集線上的 Crash,這里直接以友盟的為例,先看下 Crash 信息:

這里可以看出這個錯誤的原因是數(shù)組越界了,那么問題來了,我們并不知道是哪里越界,上面只給出了一個內(nèi)容地址:
5 YHRSS 0x1000420b0 YHRSS + 270512
6 YHRSS 0x100041378 YHRSS + 267128
這時就可以通過 dSYM 文件來定位出問題的地方了。首先通過 archives 來找到 dSYM 文件:
步驟1:

步驟2:

步驟3:

我們 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)境。
常用命令
-
help 最牛逼的命令
help 可以輸出 LLDB 的命令,使用 help <command> 可以輸出相應(yīng)命令的 help。圖片.png
-
po、p 打印值
圖片.pngpo 和 p 的區(qū)別在于使用po只會輸出對應(yīng)的值,p 則會返回值的類型以及命令結(jié)果的引用名。
-
exp 輸出或修改值(主要作用是修改值)
圖片.png -
bt 當前線程的調(diào)用堆棧,可能通過后面添加數(shù)字來限制輸出線程數(shù),如 bt 5,只輸出前5個。
52245C67-3376-4A95-A323-A3875BEBF2F9.png -
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)于概念,這里就不詳細介紹了,直接進行實際操作:
-
通過 xcode 中的 product --> profile 來啟動 Instrument,并選擇 Time Profiler 工具:
BAE9B1B4-19D3-447D-9D59-42955BC22114.png -
運行 Time Profiler,配置顯示方式,分線程顯示和隱藏系統(tǒng)的無關(guān)內(nèi)容:
B6CA42E2-0AEC-4787-AA39-C1B48FD7CF1F.png -
在手機上執(zhí)行想要測試的操作,執(zhí)行完后停止 Time Profiler 進行分析:
CBE4F6B3-6DD8-4CC0-8280-8F15A599B425.png -
找到主要耗時的地方,并定位到具體的代碼行(點擊方法的小箭頭就可以進入相應(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)的位置,這樣才方便分析問題:

因為 Leaks 的使用和 Time Profiler 是一樣的,這里就不去重復(fù)描述使用過程,在這里簡單地介紹下會出現(xiàn)內(nèi)存泄露的常見情況:
- 循環(huán)引用
- ARC 中使用 C 方式開辟的內(nèi)存沒有手動釋放
- 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)的文章,畢竟這不是一兩句話就能說清楚的事情。








