對(duì)于軟件開發(fā)而言美莫,調(diào)試是必須學(xué)會(huì)的技能爪瓜,重要性不言而喻蹬跃。對(duì)于調(diào)試的技能,基本上是可以遷移的铆铆,也就是說你以前在其他平臺(tái)上掌握的很多調(diào)試技巧蝶缀,很多也是可以用在iOS開發(fā)中丹喻。不同語言、不同IDE翁都、不同平臺(tái)的調(diào)試碍论,有同性也有個(gè)性。今天我們就來學(xué)習(xí)一下iOS開發(fā)中的調(diào)試技巧荐吵,語言暫用為OC骑冗,IDE當(dāng)然是強(qiáng)大的Xcode。首先說明下先煎,Xcode已經(jīng)為我們調(diào)試項(xiàng)目提供了極大的方便贼涩。
【1.普通斷點(diǎn)】
斷點(diǎn)(Breakpoint)絕對(duì)是調(diào)試程序的第一大選擇,也是掌握的基礎(chǔ)技能薯蝎。顧名思義遥倦,當(dāng)程序運(yùn)行到斷點(diǎn)處時(shí)會(huì)暫停運(yùn)行。比如斷點(diǎn)打在11行占锯,那么程序就會(huì)停在11行(注意:程序只運(yùn)行到了前10行袒哥,第11行其實(shí)還沒有被執(zhí)行!O浴1こ啤)。只要在代碼行旁邊點(diǎn)擊艺演,就能添加一個(gè)斷點(diǎn)却紧,再次點(diǎn)擊,就能讓斷點(diǎn)不可用(disable了胎撤,仍然存在晓殊,只是不起作用了)。在某一行創(chuàng)建斷點(diǎn)的快捷鍵是:command+\
并能在調(diào)試過程中在下方看到參數(shù)的值:
【2.條件斷點(diǎn)】
以上的斷點(diǎn)只是最普通的伤提,我們還能對(duì)斷點(diǎn)的屬性進(jìn)行配置巫俺,設(shè)置條件,使斷點(diǎn)更加智能化肿男,右鍵斷點(diǎn)進(jìn)入編輯對(duì)話框:
Edit BreakPoint... :編輯斷點(diǎn)介汹。
Disable BreakPoint :斷點(diǎn)失效。(相當(dāng)于上邊說到的單擊斷點(diǎn)變成淺藍(lán)色舶沛,斷點(diǎn)失效)
Delete BreakPoint :刪除斷點(diǎn)嘹承。
Reveal in BreakPoint Navigator :在左邊的斷點(diǎn)樹狀結(jié)構(gòu)表明該斷點(diǎn)。
我以一個(gè)循環(huán)作為測(cè)試代碼:
循環(huán)中的代碼每次都要單步執(zhí)行冠王,可能這并不是我想要的。我想要在i為3的時(shí)候中斷程序舌镶,進(jìn)行調(diào)試柱彻,編寫條件如下:
豪娜。
設(shè)置i==3的條件后,程序就會(huì)在該條件時(shí)中斷哟楷,而不會(huì)每次到達(dá)該位置都中斷瘤载。中斷時(shí)輸出如下:
同時(shí)也可以設(shè)置Ignore參數(shù),會(huì)忽略前面n次的斷點(diǎn)運(yùn)行卖擅,會(huì)在第n+1次中斷鸣奔。
。
調(diào)試輸出如下:
同時(shí)惩阶,還可以查看某個(gè)函數(shù)被調(diào)用的次數(shù)挎狸,設(shè)置Action參數(shù)如下,注意要選中Automatically continue after evaluating actions.
輸出結(jié)果如下:
断楷。
【1】Condition:返回一個(gè)布爾值锨匆,當(dāng)布爾值為真觸發(fā)斷點(diǎn),一般里面我們可以寫一個(gè)表達(dá)式冬筒。
【2】Ignore: 忽略前N次斷點(diǎn),到N+1次再觸發(fā)斷點(diǎn)舞痰。
【3】Action: 斷點(diǎn)觸發(fā)事件土榴,分為六種:
<1> AppleScript:執(zhí)行腳本。
<2> Capture GPU Frame:用于OpenGL ES調(diào)試响牛,捕獲斷點(diǎn)處GPU當(dāng)前繪制幀玷禽。
<3> Debugger Command:和控制臺(tái)中輸入LLDB調(diào)試命令一致。
<4> Log Message:輸出自定義格式信息至控制臺(tái)娃善。
<5> Shell Command:接收命令文件及相應(yīng)參數(shù)列表论衍,Shell Command是異步執(zhí)行的,只有勾選“Wait until done”才會(huì)等待Shell命令執(zhí)行完在執(zhí)行調(diào)試聚磺。
<6> Sound:斷點(diǎn)觸發(fā)時(shí)播放聲音坯台。
【4】Options(Automatically continue after evaluating actions選項(xiàng)):選中后,表示斷點(diǎn)不會(huì)終止程序的運(yùn)行瘫寝。
【3.異常斷點(diǎn)】
斷點(diǎn)的功能不限于上面所述蜒蕾。開發(fā)iOS知道,如果我們因?yàn)楫惓H缓蟪绦騝rash了焕阿,代碼就直接跑到main.m的main函數(shù)中去了咪啡。為什么就不能跑到出現(xiàn)異常的代碼中呢?暮屡?撤摸?異常斷點(diǎn)就為我們解決該問題,程序就會(huì)在異常出現(xiàn)的那行代碼終止。創(chuàng)建異常斷點(diǎn)圖例如下:
如下所示就創(chuàng)建完成了准夷。如果碰到異常crash時(shí)钥飞,嘗試使用異常斷點(diǎn)吧。
【4.符號(hào)斷點(diǎn)Symbolic Breakpoint】
符號(hào)斷點(diǎn)的創(chuàng)建也同異常斷點(diǎn)衫嵌。一般符號(hào)斷點(diǎn)可以在你指定的[類名 方法名]時(shí)中斷執(zhí)行读宙。
配置符號(hào)斷點(diǎn)如下:可以當(dāng)執(zhí)行到ViewController類的viewDidLoad方法時(shí)中斷執(zhí)行。
如果你的Symbol只寫了一個(gè)函數(shù)名楔绞,那么就會(huì)在出現(xiàn)該函數(shù)名的地方就中斷執(zhí)行结闸。如下,就會(huì)在運(yùn)行到doAnimation的時(shí)候中斷酒朵。是不是很強(qiáng)大呢桦锄?
【5.Analyze分析器】
Analyze分析器是一種靜態(tài)的工具,可以對(duì)我們的程序進(jìn)行分析耻讽,找出我們未使用的變量察纯,或一些死存儲(chǔ)。執(zhí)行Analyze如下:Product-->Analyze. 如下藍(lán)色的標(biāo)記就是靜態(tài)分析的結(jié)果针肥。
當(dāng)然饼记,我們可以設(shè)置在編譯程序的時(shí)候同時(shí)Analyze,把下列選項(xiàng)設(shè)為Yes即可慰枕。
【6.Profile檢查器】
這個(gè)工具實(shí)在是太NB了具则,三言兩語說不完,貼張圖具帮,大家感受一下博肋,我會(huì)在以后的博客中慢慢講解該工具的使用。同樣在Product-->Profile中打開蜂厅。
【7.僵尸對(duì)象】
iOS中把那些已經(jīng)release但還沒完全消失的對(duì)象叫做僵尸對(duì)象匪凡,對(duì)已經(jīng)release的對(duì)象再次釋放,就會(huì)發(fā)生異常掘猿。雖然自從使用ARC后病游,由于對(duì)象釋放產(chǎn)生的異常已經(jīng)大大變少,但偶爾還會(huì)出現(xiàn)稠通。開啟僵尸對(duì)象模式后衬衬,就能快速定位到異常位置。開啟方式如下:Product-->Scheme-->Edit Scheme. 勾選Enable Zombie Objects即可改橘。
【8.lldb命令】
Xcode中使用llvm編譯器滋尉,公認(rèn)為最好的C、C++飞主、OC狮惜、Swift編譯器高诺。而lldb是llvm中的調(diào)試器,我們可以使用一些簡(jiǎn)單的命令進(jìn)行調(diào)試碾篡,我還是把上面的循環(huán)代碼作為測(cè)試代碼懒叛。
斷點(diǎn)調(diào)試中,使用po命令耽梅、print命令在Console控制臺(tái)打印出變量信息:
【9.NSLog打印】
應(yīng)該說NSLog打印信息是初學(xué)者最喜歡的調(diào)試手法,也是最簡(jiǎn)單的調(diào)試胖烛,通過打印出的信息查看程序運(yùn)行的路徑眼姐。但是打印出的信息較少,本身NSLog效率較低佩番,有人使用宏做了部分優(yōu)化众旗,代碼如下:能夠打印出所在類名、所在方法名趟畏、詳細(xì)時(shí)間贡歧、行號(hào)。
#import "ViewController.h"
#define NSLog(format, ...)do{ \
fprintf(stderr,"<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr,"-------\n"); \
} while (0)
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
for(int i =0; i <5; i++) {
NSLog(@"我的值:%d",i);
}
}
@end
打印結(jié)果如下:
【10.生命周期方法init,dealloc】
對(duì)于ViewController來說赋秀,有兩個(gè)生命周期函數(shù)我們可以進(jìn)行重寫利朵,也就是init和dealloc方法。對(duì)于某些對(duì)象的狀態(tài)猎莲,我們可以在這兩個(gè)方法中查看绍弟。尤其是在dealloc中可以看到當(dāng)ViewController退出的時(shí)候某個(gè)對(duì)象是否release。
- (instancetype)init
{
self = [super init];
if(self) {
//初始化語句著洼;
}
return self;
}
- (void)dealloc
{
//釋放后調(diào)用樟遣;
}
【11.查看代碼運(yùn)行時(shí)間】
有時(shí)候我們想要準(zhǔn)確的知道某段代碼、某個(gè)循環(huán)執(zhí)行的時(shí)間身笤,然后分析效率等問題豹悬,這個(gè)時(shí)候就需要執(zhí)行時(shí)間是多少。正好看到網(wǎng)上已經(jīng)有人做了這個(gè)工作液荸,我就直接摘下來了瞻佛。正好也用了宏的方式計(jì)算時(shí)間,我們只要在需要計(jì)算時(shí)間的代碼塊前后寫上TICK,TOCK宏即可莹弊。當(dāng)然涤久,原理也是非常的簡(jiǎn)單,也就是使用NSDate計(jì)算差值忍弛。
#import "ViewController.h"
#define TICK NSDate *startTime = [NSDate date]
#define TOCK NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
TICK;
for(int i =0; i <5; i++) {
NSLog(@"我的值:%d",i);
}
TOCK;
}
@end
打印結(jié)果如下:
【12.手機(jī)截屏】
手機(jī)截屏也算調(diào)試响迂?哈哈。其實(shí)也算是開發(fā)中的一個(gè)小技巧哈细疚。其實(shí)大家都會(huì)在iPhone上同時(shí)按電源鍵+Home鍵截屏蔗彤,然后使用各種通訊軟件發(fā)給其他人川梅,這個(gè)略顯不方便。我們來使用Xcode中的方式截屏然遏。當(dāng)手機(jī)接上電腦后贫途,注意要把調(diào)試設(shè)備選為自己的手機(jī):
然后選擇Debug-->View Debugging-->Take Screenshot... .然后可以看到手機(jī)屏幕已經(jīng)在你的電腦桌面了。是不是比直接在手機(jī)上操作方便多了呢待侵?這樣就可以快速的發(fā)給其他開發(fā)者丢早、PM等人了。
【13.viewDidLoad不建議寫太多代碼】
個(gè)人建議不要在viewDidLoad方法中寫入太多代碼秧倾。尤其是涉及該界面中的動(dòng)畫的時(shí)候怨酝,因?yàn)閳?zhí)行viewDidLoad方法的時(shí)候,界面可能還沒完全加載出來那先,如果此時(shí)把動(dòng)畫放在viewDidLoad中农猬,可能會(huì)造成動(dòng)畫無法顯示。當(dāng)然也不建議把耗時(shí)的網(wǎng)絡(luò)請(qǐng)求和動(dòng)畫效果都放在viewDidLoad中售淡,界面的阻塞也會(huì)造成動(dòng)畫無法顯示斤葱。可以嘗試把動(dòng)畫放在viewDidAppear,viewWillAppear方法中揖闸。對(duì)于這類涉及UI的問題揍堕,調(diào)試也是比較麻煩的。汤纸。鹤啡。
【14.視圖調(diào)試】
如今iOS開發(fā)的UI設(shè)計(jì)有很多種方式,比如storyboard蹲嚣,xib递瑰,代碼實(shí)現(xiàn)。對(duì)于stoayboard,xib可視化實(shí)現(xiàn)是比較簡(jiǎn)單的隙畜,但是對(duì)于一些“iOS老程序員”而言抖部,都喜歡使用代碼實(shí)現(xiàn)UI,并且可能UI層次還比較復(fù)雜议惰。這樣就給我們新接手項(xiàng)目的開發(fā)者帶來很多困擾慎颗。如何快速查看一個(gè)復(fù)雜UI的界面層次和布局,最快的方法就是用到視圖調(diào)試言询。
當(dāng)項(xiàng)目運(yùn)行到某一個(gè)界面(可以是模擬器或真機(jī))時(shí)俯萎,開啟視圖調(diào)試,點(diǎn)擊按鈕如圖:
這樣就會(huì)進(jìn)入試圖調(diào)試运杭,你可以很方便的查看這個(gè)界面夫啊。這里可以看到控件之間的層次關(guān)系。
左側(cè)的樹形層次圖可以在查看線程辆憔、隊(duì)列和UI之間切換:
【15】常用的編譯宏定義:可以讓代碼在不同的編譯情況下執(zhí)行撇眯。
1)OPTIMIZE :用于release和debug的判斷报嵌,當(dāng)選擇了OPTIMIZE 時(shí),可以讓代碼在release時(shí)執(zhí)行熊榛,在debug時(shí)不執(zhí)行锚国。示例如下:
#ifndef __OPTIMIZE__
//這里執(zhí)行的是debug模式下
else
//這里執(zhí)行的是release模式下
#endif
2)i386 與 x86_64 :用于模擬器環(huán)境和真機(jī)環(huán)境的判斷。滿足該條件的代碼只在模擬器下執(zhí)行玄坦。示例代碼如下:
#if defined (__i386__) || defined (__x86_64__)
//模擬器下執(zhí)行
#else
//真機(jī)下執(zhí)行
#endif
(3)__IPHONE_OS_VERSION_MAX_ALLOWED :當(dāng)前編譯的SDK版本血筑,可以與__IPHONE_9_0等宏定義進(jìn)行比較,進(jìn)行不同版本下代碼的執(zhí)行煎楣。示例如下:
if (__IPHONE_OS_VERSION_MAX_ALLOWED == __IPHONE_9_0) {
//如果當(dāng)前SDK版本為9.0是執(zhí)行這里的代碼
}else{
//否則執(zhí)行這里
}
【16】預(yù)編譯宏在開發(fā)調(diào)試中非常有用云挟,我們來仔細(xì)實(shí)踐一下:
(1)if的預(yù)編譯命令,根據(jù)后面的條件判斷是否執(zhí)行转质,因?yàn)檫@里條件為1,始終為真帖世,所以在#if...#endif中的代碼一定會(huì)執(zhí)行休蟹。
#if 1
//這里的代碼一定會(huì)執(zhí)行
#endif
(2)這里的條件為0,為假日矫,所以#if...#endif里面的代碼一定不會(huì)執(zhí)行赂弓。
#if 0
//這里的代碼一定不會(huì)執(zhí)行
#endif
(3)預(yù)編譯命令還能根據(jù)是否宏定義某個(gè)標(biāo)志,來選擇是否執(zhí)行哪轿。
#ifdef MY_TOP
//如果 MY_TOP 被宏定義過盈魁,那么里面的代碼會(huì)執(zhí)行,否則不會(huì)執(zhí)行窃诉。
#endif
(4)預(yù)編譯命令還可以進(jìn)行嵌套杨耙,就像普通的條件判斷一樣。
#ifdef MY_TOP
//如果 MY_TOP 被宏定義過飘痛,那么里面的代碼會(huì)執(zhí)行珊膜,否則不會(huì)執(zhí)行。
#if 1
//根據(jù)條件判斷是否執(zhí)行
#endif
#ifdef MY_TOP2
//嵌套判斷
#endif
#endif
(5)既然有#ifdef...#endif,相反的宣脉,也有#ifndef...#endif,#ifndef的作用正好和#ifdef相反车柠,如果宏定義沒有被聲明,那么將會(huì)執(zhí)行執(zhí)行塑猖;如果宏定義被聲明了竹祷,就不會(huì)執(zhí)行下面的代碼。
#ifndef YOU_TOP
//如果 YOU_TOP 沒有被聲明羊苟,就會(huì)執(zhí)行里面的代碼塑陵,否則不會(huì)執(zhí)行。
#endif
(6)既然是類似條件判斷蜡励,那么就不得不說#else猿妈,在條件編譯中吹菱,同樣有#if...#else的判斷,執(zhí)行邏輯和普通的條件判斷一樣彭则。
#ifdef MY_TOP
//如果 MY_TOP 被宏定義過鳍刷,那么這里的代碼會(huì)執(zhí)行,否則不會(huì)執(zhí)行俯抖。
#else
//要么執(zhí)行else外面的输瓜,要么執(zhí)行else里面的。
#if 1
//根據(jù)條件判斷是否執(zhí)行
#endif
#endif
7)每出現(xiàn)一次if,就一定要有#endif去包裹芬萍,否則預(yù)編譯器會(huì)提示你錯(cuò)誤尤揣。如果有多層的嵌套,不同層次需要有適當(dāng)?shù)目s進(jìn)柬祠。
(8)對(duì)于項(xiàng)目暫時(shí)不執(zhí)行的代碼北戏,不建議使用注釋代碼去禁用。推薦使用預(yù)編譯命令去打開關(guān)閉一段代碼漫蛔。
【17】#warning的使用
有時(shí)候在代碼中出現(xiàn)大量黃色的警告是非常煩人的事情嗜愈,大量的警告不利于代碼的維護(hù)與調(diào)試。但是有時(shí)候我們需要手動(dòng)去打一些警告莽龟,讓我們記住這里應(yīng)該要注意些什么重要的事情蠕嫁。在code review中,別人也會(huì)打一些#warning,提示你應(yīng)該要注意什么問題毯盈,方便我們?nèi)ゲ檎覇栴}和修改剃毒。
總結(jié),調(diào)試不僅僅是我上面提到的技巧搂赋,更多的是長(zhǎng)年累月積累下來的經(jīng)驗(yàn)赘阀,只有在自己的開發(fā)中不斷的出錯(cuò)、試錯(cuò)脑奠、調(diào)錯(cuò)纤壁、解決錯(cuò)誤的過程中才能提高自己的編程水平和調(diào)試能力。我會(huì)繼續(xù)更新該篇博客捺信,講解更多的調(diào)試技能酌媒,希望我們都能在實(shí)踐中提高進(jìn)步。
原文鏈接: https://blog.csdn.net/chenyufeng1991/article/details/50478656