對于大型APP,每次修改重新編譯,都需要幾分鐘時(shí)間,因此要在一次運(yùn)行中盡量多的解決問題,減少編譯的次數(shù)。
無論是需求的迭代,還是bug的調(diào)試,都可能會遇到陌生的代碼,對于不熟悉的代碼,如何調(diào)試呢?
一 打Log
只需要一行輸出函數(shù),即可看到各個(gè)方法的執(zhí)行順序,知道輸入、輸出值,但是對代碼入侵性太強(qiáng),并且頻繁修改消耗過多編譯的時(shí)間。
二 問寫代碼的人
最直接最高效。但是如果不經(jīng)過思考,就直接問,下次遇到類似的問題還是不會。因此我認(rèn)為最好先自己調(diào)試,如果幾個(gè)小時(shí)還無法解決,再請教。
對于遇到的問題,不能僅僅當(dāng)成一種需要完成的任務(wù),而應(yīng)該把它當(dāng)成一種成長的機(jī)會,擁有一顆成長型的心態(tài)。解決問題的同時(shí),自己也獲得了成長,這樣就不僅僅是自己在跑步,而是借助公司這輛汽車,達(dá)到遠(yuǎn)超跑步的速度。
三 控制臺調(diào)試
控制臺調(diào)試最核心的就是LLDB命令,LLDB是Xcode的默認(rèn)調(diào)試器,它高度利用LLVM項(xiàng)目中的現(xiàn)有庫,例如Clang表達(dá)式解析器和LLVM反匯編程序。
3.1 執(zhí)行命令:
expression命令,簡寫e,最基本的命令。
運(yùn)用實(shí)例:修改View的顏色
有時(shí)候我們需要找某個(gè)控件,或改某個(gè)控件的顏色,但是可能因?yàn)閷蛹壿^多而不是非常明顯的情況下,可以先在LLDB嘗試修改,看一下預(yù)覽的效果。

操作步驟如下:
1.先執(zhí)行
(lldb) p self.view
查看self.view存儲在變量 0強(qiáng)轉(zhuǎn)為UIView類型,
(lldb) e (UIView *)1
3.使用 1.layer.backgroundColor=[UIColor redColor].CGColor
4.執(zhí)行刷新,可以看到對應(yīng)的UIView立刻生效了。因?yàn)閿帱c(diǎn)會終止UI線程,所以這個(gè)命令是為了渲染修改后的界面。
(lldb) e [CATransaction flush]
實(shí)際運(yùn)用 暗黑適配
我們團(tuán)隊(duì)今年進(jìn)行了暗黑模式適配,在改別人的代碼時(shí),尤其是層級非常多的時(shí)候,用此來調(diào)整、預(yù)覽,可以快速定位需要修改的控件。
3.2 打印命令
1)print:簡寫pri或p,其實(shí)print是"expression --"的簡寫('print' is an abbreviation for 'expression --')。
2)po:用來打印對象
3)call:調(diào)用方法。在調(diào)試時(shí),想要額外調(diào)用一下某個(gè)方法,可以使用call命令,如圖
call [self callMethod]

3.3 線程
1)thread jump --by N
功能:在某處斷點(diǎn),跳過之后的N行代碼。其效果等同于注釋了N行代碼。
使用場景:有時(shí)候需要注釋幾行代碼,使用此命令,不用重新編譯即可立刻看到效果。
2)thread backtrace 查看線程堆棧信息,簡寫為bt。
3)thread return X
令某個(gè)方法直接返回X。
運(yùn)用實(shí)例:
(lldb) thread return XXX

如圖,在25行執(zhí)行了
(lldb) thread return @"changeReturn"
可以看到- (NSString *)threadReturn 返回的值就變成了@"changeReturn"。
3.4 觀察點(diǎn) watchpoint
作用:可以觀察某個(gè)變量,在變化時(shí)其會自動暫停斷點(diǎn)至其相應(yīng)位置。
使用方式:(lldb) watchpoint set variable name
或者右鍵其,點(diǎn)擊watch variable
注意:如果需要觀測self.name,則要寫成self->_name。對于觀測普通類型的變量,比如button的 state值,_state不存在。在LLDB調(diào)試框可以右鍵這個(gè)值。
使用場景:當(dāng)某個(gè)變量值突然被改變,但是又找不到是在哪里改變。使用此即可跟蹤其變化。
如果不使用變量觀測,就需要重寫某個(gè)類的某個(gè)setter方法。
四、斷點(diǎn)調(diào)試
先來介紹一下基本的界面:

這5個(gè)按鈕,分別是:
1 啟動/禁用斷點(diǎn)
2 繼續(xù)執(zhí)行程序
3 執(zhí)行下一步
4 進(jìn)入方法
5 跳出方法
這幾個(gè)可視化的按鈕,在LLDB中都有相應(yīng)的命令,
1 breakpoint enable/disabled
2 continue
3 next
4 step
5 finish
但是因?yàn)榘粹o更方便,所以通常也不用命令。
4.1 異常斷點(diǎn)
異常斷點(diǎn)是為了拋出異常時(shí),crash顯示到具體的行,而不是main.m中。加上如圖的斷點(diǎn)即可。

4.2 符號斷點(diǎn)
符號斷點(diǎn)是針對某個(gè)方法執(zhí)行的斷點(diǎn),
實(shí)際開發(fā)中,當(dāng)我們操作了某個(gè)路徑會執(zhí)行A方法,但是A方法用于很多個(gè)地方,并不知道具體是在哪里執(zhí)行的,如果是傳統(tǒng)的方法,就全局搜索A方法,全部打上斷點(diǎn)。實(shí)際上可以用符號斷點(diǎn)來解決這個(gè)問題。
只要對某個(gè)方法打上符號斷點(diǎn),那么所有調(diào)用它的地方,都會暫停。
注意:+,-方法寫清楚。:之后不能有空格
另外。對于寫得比較標(biāo)準(zhǔn)的代碼,幾個(gè)類似的方法,最終應(yīng)該調(diào)用一個(gè)統(tǒng)一的方法,叫做全能初始化方法,用NS_DESIGNATED_INITIALIZER標(biāo)識。只需要對全能初始化方法打符號斷點(diǎn)即可。
4.3 編輯斷點(diǎn)

右鍵點(diǎn)擊某一行,即可打開:

1)Condition
條件斷點(diǎn),對于循環(huán)次數(shù)非常多的for循環(huán),有時(shí)我們需要其在指定的i值時(shí)才暫停,或者某兩個(gè)變量相等時(shí)才暫停。
2)Ignore
忽視次數(shù)N,即N次之后才會暫停。
3)Action
有些問題通過斷點(diǎn)無法解決,因?yàn)閿帱c(diǎn)操作需要幾秒的操作時(shí)間。而輪詢類的邏輯,幾秒之后其值就變化了。這類問題通常是需要打Log的,但是打Log重新編譯消耗過多的時(shí)間,如何在不改變代碼、重新編譯的前提下打Log呢?這時(shí)Action就起了很大的作用。
(1)Debugger Command
這個(gè)功能相當(dāng)強(qiáng)大,可以在某一行直接插入新的代碼并且不用重新編譯。
(2)Log Message
打Log,格式:@exp@,exp指的是變量名。%B:指的是此行所在的方法名。%H:計(jì)數(shù)器。
缺點(diǎn):Log Message 適合低頻的打印。如果是秒級更新的Log。不適用,每秒打印一次會非??ā?br>
(3)Sound:當(dāng)斷點(diǎn)到這里時(shí),發(fā)出聲音,作用和Log一樣,只不過從文字變成了聲音。
(4)AppleScript,可以執(zhí)行蘋果原生的腳本。
(5)Shell Command,可以執(zhí)行shell腳本。
五 視圖查看
有時(shí)候需要對可視的某個(gè)控件進(jìn)行需求,這種情況可以通過視圖結(jié)構(gòu),直接找到對應(yīng)的類名。雖然不能直接解決問題,但是對于找到問題根源所在,有一定幫助。
5.1原生的Debug View Hierarchy
Debug View Hierarchy,無論是性能性,還是操作方便度上,都比較差。
5.2 Reveal
reveal這是一個(gè)專業(yè)的團(tuán)隊(duì)做的軟件,非常好用,只不過需要一些配置。
詳見http://www.itdecent.cn/p/ced27bec87b4

5.3 Chisel
Chisel的visualize,facebook出的一個(gè)開源LLDB框架,可以在斷點(diǎn)的時(shí)候查看某個(gè)類的視圖。
六 LLDB擴(kuò)展
facebook開源了chisel,可以添加腳本到LLDB。
源碼:https://github.com/facebook/chisel
chisel提供了幾個(gè)命令,對LLDB的功能進(jìn)一步加強(qiáng)。
1)visualize:預(yù)覽整個(gè)view的圖。
相比原生的命令,看到的不僅僅是一個(gè)控件的圖像,而可以是多個(gè)view的組合。
七 網(wǎng)絡(luò)請求的抓包
對于網(wǎng)絡(luò)請求,可以在請求的回調(diào)中打斷點(diǎn)、Log,查看返回的結(jié)果。但是對于輪詢的請求,打斷點(diǎn)太慢,打Log在調(diào)試框中展示的內(nèi)容又太多,會刷屏。因此可以用抓包工具,對所有的請求都進(jìn)行了整理。
1 charles:古老的抓包工具,不想買軟件的話可以免費(fèi)使用。
2 proxyman,新起之秀,個(gè)人認(rèn)為無論是基礎(chǔ)功能,還是界面,都已經(jīng)超越了Charles。

3 wireshark,適用于基于TCP/UDP或藍(lán)牙協(xié)議的硬件的抓包。
八 靜態(tài)分析
靜態(tài)分析,可以在編碼的階段就能檢測出一些潛在的問題。
原生的Analyze,以及三方的Clang,Infer,OCLint。
九 參考文獻(xiàn)
http://lldb.llvm.org/
http://www.itdecent.cn/p/d0b9fd8ffadc
https://developer.apple.com/videos/play/wwdc2018/412/?time=182