1. 常用調(diào)試方式
Print VS 單步調(diào)試
說(shuō)到調(diào)試,剛?cè)腴T(mén)編程時(shí),用得最多的無(wú)疑是 print,畢竟連教材都是這樣寫(xiě)的,直接打印,簡(jiǎn)單明了。但是當(dāng)打印內(nèi)容太多時(shí),就容易看得頭暈?zāi)X脹了,這里以 Swift 為例,稍微改進(jìn)下 print 方法:
/**
print log
#file String 包含這個(gè)符號(hào)的文件的路徑
#line Int 符號(hào)出現(xiàn)處的行號(hào)
#column Int 符號(hào)出現(xiàn)處的列
#function String 包含這個(gè)符號(hào)的方法名字
*/
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
}
打印的時(shí)候輸入文件的路徑,行列號(hào)和方法明等,這樣更方便通過(guò) print 的日志來(lái)精準(zhǔn)定位問(wèn)題。但是通過(guò) print 來(lái)調(diào)試時(shí),有時(shí)候就不容易發(fā)現(xiàn)一些邏輯上的問(wèn)題,或者需要使用大量的 print 輸出日志,這個(gè)時(shí)候就可以考慮使用單步調(diào)試:

單步調(diào)試的時(shí)候可以逐步查看代碼的執(zhí)行過(guò)程,是解決 bug 的神器,如果你是一位新手,這肯定是你首要學(xué)會(huì)的技能,在單步調(diào)試的時(shí)候,一般都會(huì)結(jié)合 LLDB 命令來(lái)使用,關(guān)于 LLDB 的內(nèi)容下面會(huì)詳細(xì)說(shuō)。
但在實(shí)際開(kāi)發(fā)中,有些場(chǎng)景是無(wú)法通過(guò)單步調(diào)試來(lái)復(fù)現(xiàn)的,比如說(shuō)多線程的場(chǎng)景下,在使用單步時(shí),很多時(shí)候是無(wú)法復(fù)現(xiàn)真實(shí)場(chǎng)景的,這個(gè)時(shí)候就需要使用萬(wàn)能的 print 了??傊?,兩種方式是必不可少的,在實(shí)際開(kāi)發(fā)中,很多時(shí)候我們不會(huì)直接使用 print,一般都會(huì)使用日志框架,來(lái)對(duì)日志進(jìn)行和記錄和收集。
Crash Report
在我們平常的開(kāi)發(fā)中,你提交測(cè)試包給 QA 測(cè)試時(shí),他那邊出現(xiàn)在了 crash,但卻不是調(diào)試模式下,這里我們就可以通過(guò)通過(guò)測(cè)試設(shè)備,在 Xcode 的 Devices 中把 crash 日志導(dǎo)出來(lái):

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

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

步驟2:

步驟3:

我們 cd 到該文件目錄下,然后執(zhí)行:
atos -arch arm64 -o YHRSS 0x1000420b0
注意這里的 -arch 是和上面 crash 報(bào)告中的對(duì)應(yīng),否則是看不到相應(yīng)的信息的:
$ atos -arch arm64 -o YHRSS 0x1000420b0
specialized YHArticlesViewController.tableView(_:heightForRowAt:) (in YHRSS) (YHArticlesViewController.swift:215)
這樣我們能就精準(zhǔn)地獲取 crash 出現(xiàn)的具體位置,然后就該是發(fā)揮自我價(jià)值的時(shí)候了。
那些項(xiàng)目中遇到的常見(jiàn)問(wèn)題定位
循環(huán)引用快速定位和解決
如果你懷疑存在循環(huán)引用,你可以 Instrument 工具來(lái)定位,但這也太麻煩了,你可以直接在 deinit{} 方法( OC 中對(duì)應(yīng)的就是 dealloc 方法)里面打一個(gè)斷點(diǎn),如果頁(yè)面退出時(shí)沒(méi)有執(zhí)行到該處,就說(shuō)明該頁(yè)面存在循環(huán)引用,頁(yè)面內(nèi)存沒(méi)有辦法釋放。
如果存在循環(huán)引用,那么首先要檢查的是 block 里面的 self 是不是需要 weak,自定義的 delegate 是不是寫(xiě)成了 strong,絕大部分都是這兩個(gè)原因?qū)е碌模饌€(gè)去檢查就好。
2. LLDB 常用命令的使用
什么是 LLDB
LLDB 是 Xcode 內(nèi)置的調(diào)試工具,它與 LLVM 編譯器一起,給開(kāi)發(fā)者提供更豐富的流程控制和數(shù)據(jù)檢測(cè)的調(diào)試功能,它的主要功能是為 Xcode 提供底層調(diào)試環(huán)境。
常用命令
-
help 最牛逼的命令
help 可以輸出 LLDB 的命令,使用 help <command> 可以輸出相應(yīng)命令的 help。圖片.png
-
po、p 打印值
圖片.pngpo 和 p 的區(qū)別在于使用po只會(huì)輸出對(duì)應(yīng)的值,p 則會(huì)返回值的類(lèi)型以及命令結(jié)果的引用名。
-
exp 輸出或修改值(主要作用是修改值)
圖片.png -
bt 當(dāng)前線程的調(diào)用堆棧,可能通過(guò)后面添加數(shù)字來(lái)限制輸出線程數(shù),如 bt 5,只輸出前5個(gè)。
52245C67-3376-4A95-A323-A3875BEBF2F9.png -
thread return 跳出當(dāng)前方法的執(zhí)行(thread return 0 設(shè)置返回值),但在 swift 中,是無(wú)法使用的,已知的問(wèn)題了,只能等待修復(fù)吧,這里給個(gè) OC 的例子:
51084CDC-031A-490C-A0BE-474DA5C6B423.png
3. Instrument 的使用
寫(xiě)在最前面,在做性能測(cè)試的時(shí)候,不能用模擬器,用真機(jī),用真機(jī),用真機(jī),重要的事情說(shuō)三次。
Time Profiler
time profile 是時(shí)間分析工具,主要用來(lái)檢測(cè)應(yīng)用 CPU 的使用情況,可以看到應(yīng)用程序中各個(gè)方法消耗 CPU 時(shí)間。關(guān)于概念,這里就不詳細(xì)介紹了,直接進(jìn)行實(shí)際操作:
-
通過(guò) xcode 中的 product --> profile 來(lái)啟動(dòng) Instrument,并選擇 Time Profiler 工具:
BAE9B1B4-19D3-447D-9D59-42955BC22114.png -
運(yùn)行 Time Profiler,配置顯示方式,分線程顯示和隱藏系統(tǒng)的無(wú)關(guān)內(nèi)容:
B6CA42E2-0AEC-4787-AA39-C1B48FD7CF1F.png -
在手機(jī)上執(zhí)行想要測(cè)試的操作,執(zhí)行完后停止 Time Profiler 進(jìn)行分析:
CBE4F6B3-6DD8-4CC0-8280-8F15A599B425.png -
找到主要耗時(shí)的地方,并定位到具體的代碼行(點(diǎn)擊方法的小箭頭就可以進(jìn)入相應(yīng)的代碼處):
11C6F965-91F3-4A44-ACF8-1F2FC03F2DC3.png這里可以看出,主要有兩一個(gè)耗時(shí)的操作,但明顯后面那我格式轉(zhuǎn)換我們沒(méi)有辦法去處理,我們只能從第一個(gè)入手。它每次創(chuàng)建都比較耗時(shí),那么我們就不要多次去創(chuàng)建,因?yàn)樗看问褂玫母袷降亩际且粯拥?,這樣我們實(shí)質(zhì)上只需要?jiǎng)?chuàng)建一次就可以了。那我們有什么方法去只創(chuàng)建一次呢,首先能想到的肯定是單例,但是用單例太麻煩了,通過(guò) static 定義成一個(gè)常量就可以了,就這樣,這處的性能問(wèn)題就解決了,其它地方也可以通過(guò)同樣的方法,逐步分析和解決就可以了。
Leaks
使用的步驟幾乎和上面的一樣,這里就不重復(fù)上圖了,但在出現(xiàn)內(nèi)存泄露的地方,我們需要手動(dòng)去選取對(duì)應(yīng)的位置,這樣才方便分析問(wèn)題:

因?yàn)?Leaks 的使用和 Time Profiler 是一樣的,這里就不去重復(fù)描述使用過(guò)程,在這里簡(jiǎn)單地介紹下會(huì)出現(xiàn)內(nèi)存泄露的常見(jiàn)情況:
- 循環(huán)引用
- ARC 中使用 C 方式開(kāi)辟的內(nèi)存沒(méi)有手動(dòng)釋放
- URLSession 對(duì)象的多次創(chuàng)建,使用 AFNetworking 時(shí)也是同理
這里單獨(dú)針對(duì) URLSession 對(duì)象的多次創(chuàng)建會(huì)導(dǎo)致內(nèi)存泄露問(wèn)題單獨(dú)說(shuō)明下,其實(shí)我們只要看過(guò) 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 對(duì)象會(huì)一直持有強(qiáng)引用,導(dǎo)致無(wú)法釋放,多次創(chuàng)建就會(huì)有內(nèi)存泄露問(wèn)題,關(guān)于 session 的使用,我們應(yīng)該使用一個(gè)單例來(lái)管理。而且唯一的 session 還可以加快網(wǎng)絡(luò)請(qǐng)求,如連接復(fù)用等,這里就不詳細(xì)說(shuō),回頭有時(shí)間再單獨(dú)寫(xiě)一篇相關(guān)的文章,畢竟這不是一兩句話就能說(shuō)清楚的事情。








