1. 常用調試方式
Print VS 單步調試
說到調試埋泵,剛入門編程時,用得最多的無疑是 print,畢竟連教材都是這樣寫的丽声,直接打印礁蔗,簡單明了。但是當打印內容太多時雁社,就容易看得頭暈腦脹了浴井,這里以 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 來調試時徒坡,有時候就不容易發(fā)現(xiàn)一些邏輯上的問題撕氧,或者需要使用大量的 print 輸出日志,這個時候就可以考慮使用單步調試:
單步調試的時候可以逐步查看代碼的執(zhí)行過程喇完,是解決 bug 的神器伦泥,如果你是一位新手,這肯定是你首要學會的技能锦溪,在單步調試的時候不脯,一般都會結合 LLDB 命令來使用,關于 LLDB 的內容下面會詳細說刻诊。
但在實際開發(fā)中防楷,有些場景是無法通過單步調試來復現(xiàn)的,比如說多線程的場景下则涯,在使用單步時复局,很多時候是無法復現(xiàn)真實場景的,這個時候就需要使用萬能的 print 了是整⌒ごВ總之,兩種方式是必不可少的浮入,在實際開發(fā)中龙优,很多時候我們不會直接使用 print,一般都會使用日志框架事秀,來對日志進行和記錄和收集彤断。
Crash Report
在我們平常的開發(fā)中,你提交測試包給 QA 測試時易迹,他那邊出現(xiàn)在了 crash宰衙,但卻不是調試模式下,這里我們就可以通過通過測試設備睹欲,在 Xcode 的 Devices 中把 crash 日志導出來:
關于如何去閱讀 Crash report 和定位該 Crash 原因供炼,可以看我之前寫的文章:淺談 Crash Report一屋,這里就不再重復談。
dSYM 文件
首先來科普下什么是 dSYM 文件:
Xcode編譯項目后袋哼,我們會看到一個同名的 dSYM 文件冀墨,dSYM 是保存函數(shù)地址映射信息的文件,調試的 symbols 都會包含在這個文件中涛贯,并且每次編譯項目的時候都會生成一個新的 dSYM 文件诽嘉。
應用上架后,可以通過類似友盟統(tǒng)計等工具收集線上的 Crash弟翘,這里直接以友盟的為例虫腋,先看下 Crash 信息:
這里可以看出這個錯誤的原因是數(shù)組越界了,那么問題來了稀余,我們并不知道是哪里越界悦冀,上面只給出了一個內容地址:
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 報告中的對應雏门,否則是看不到相應的信息的:
$ 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 中對應的就是 dealloc 方法)里面打一個斷點,如果頁面退出時沒有執(zhí)行到該處步脓,就說明該頁面存在循環(huán)引用愿待,頁面內存沒有辦法釋放。
如果存在循環(huán)引用靴患,那么首先要檢查的是 block 里面的 self 是不是需要 weak仍侥,自定義的 delegate 是不是寫成了 strong,絕大部分都是這兩個原因導致的鸳君,逐個去檢查就好农渊。
2. LLDB 常用命令的使用
什么是 LLDB
LLDB 是 Xcode 內置的調試工具,它與 LLVM 編譯器一起或颊,給開發(fā)者提供更豐富的流程控制和數(shù)據(jù)檢測的調試功能砸紊,它的主要功能是為 Xcode 提供底層調試環(huán)境。
常用命令
-
help 最牛逼的命令
help 可以輸出 LLDB 的命令囱挑,使用 help <command> 可以輸出相應命令的 help醉顽。
-
po、p 打印值
po 和 p 的區(qū)別在于使用po只會輸出對應的值平挑,p 則會返回值的類型以及命令結果的引用名游添。
-
exp 輸出或修改值(主要作用是修改值)
-
bt 當前線程的調用堆棧系草,可能通過后面添加數(shù)字來限制輸出線程數(shù),如 bt 5唆涝,只輸出前5個找都。
-
thread return 跳出當前方法的執(zhí)行(thread return 0 設置返回值),但在 swift 中石抡,是無法使用的准谚,已知的問題了中狂,只能等待修復吧,這里給個 OC 的例子:
3. Instrument 的使用
寫在最前面,在做性能測試的時候德绿,不能用模擬器,用真機起胰,用真機升酣,用真機,重要的事情說三次煞茫。
Time Profiler
time profile 是時間分析工具帕涌,主要用來檢測應用 CPU 的使用情況,可以看到應用程序中各個方法消耗 CPU 時間续徽。關于概念蚓曼,這里就不詳細介紹了,直接進行實際操作:
-
通過 xcode 中的 product --> profile 來啟動 Instrument钦扭,并選擇 Time Profiler 工具:
-
運行 Time Profiler纫版,配置顯示方式,分線程顯示和隱藏系統(tǒng)的無關內容:
-
在手機上執(zhí)行想要測試的操作客情,執(zhí)行完后停止 Time Profiler 進行分析:
-
找到主要耗時的地方其弊,并定位到具體的代碼行(點擊方法的小箭頭就可以進入相應的代碼處):
這里可以看出,主要有兩一個耗時的操作膀斋,但明顯后面那我格式轉換我們沒有辦法去處理梭伐,我們只能從第一個入手。它每次創(chuàng)建都比較耗時仰担,那么我們就不要多次去創(chuàng)建糊识,因為它每次使用的格式的都是一樣的,這樣我們實質上只需要創(chuàng)建一次就可以了惰匙。那我們有什么方法去只創(chuàng)建一次呢技掏,首先能想到的肯定是單例,但是用單例太麻煩了项鬼,通過 static 定義成一個常量就可以了哑梳,就這樣,這處的性能問題就解決了绘盟,其它地方也可以通過同樣的方法鸠真,逐步分析和解決就可以了悯仙。
Leaks
使用的步驟幾乎和上面的一樣,這里就不重復上圖了吠卷,但在出現(xiàn)內存泄露的地方锡垄,我們需要手動去選取對應的位置,這樣才方便分析問題:
因為 Leaks 的使用和 Time Profiler 是一樣的祭隔,這里就不去重復描述使用過程货岭,在這里簡單地介紹下會出現(xiàn)內存泄露的常見情況:
- 循環(huán)引用
- ARC 中使用 C 方式開辟的內存沒有手動釋放
- URLSession 對象的多次創(chuàng)建,使用 AFNetworking 時也是同理
這里單獨針對 URLSession 對象的多次創(chuàng)建會導致內存泄露問題單獨說明下疾渴,其實我們只要看過 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 對象會一直持有強引用千贯,導致無法釋放,多次創(chuàng)建就會有內存泄露問題搞坝,關于 session 的使用搔谴,我們應該使用一個單例來管理。而且唯一的 session 還可以加快網絡請求桩撮,如連接復用等敦第,這里就不詳細說,回頭有時間再單獨寫一篇相關的文章店量,畢竟這不是一兩句話就能說清楚的事情芜果。