iOS SDK:iOS調(diào)試技巧
為什么你的數(shù)組包含3個(gè)項(xiàng)目而不是5個(gè)?為什么你的游戲運(yùn)行緩慢?這些都跟調(diào)試有關(guān),調(diào)試是開(kāi)發(fā)過(guò)程中必不可少的一部分。本文所列舉了一些重要的調(diào)試功能(當(dāng)然并不全面)可以幫你用更少的時(shí)間來(lái)解決bug問(wèn)題。
本文內(nèi)容主要包括3個(gè)方面:
使用console檢查app狀態(tài)
進(jìn)行日志記錄,并熟練的駕馭NSLog
使用對(duì)象的生命周期來(lái)跟蹤內(nèi)存的使用。
使用Console檢查app狀態(tài)
Xcode底部的小黑盒是我們調(diào)試時(shí)的好朋友,它可以輸出日志信息、錯(cuò)誤信息以及其他有用的東西來(lái)幫你跟蹤錯(cuò)誤,除了可以看到日志直接輸出的信息外,我們編程過(guò)程中也可以在某些斷點(diǎn)停留,來(lái)檢查app的多個(gè)方面。
條件斷點(diǎn)
我假定你知道Breakpoints是如何工作的(如果你不知道,呵呵,看完這個(gè)文章也許你就知道了?。?/p>
讓程序在某個(gè)特定的時(shí)間點(diǎn)命中斷點(diǎn)非常有價(jià)值,但要通過(guò)一個(gè)循環(huán)或者遞歸函數(shù)才能讓對(duì)象等于某個(gè)確定的值,是一件令人痛苦的事情。這時(shí)候我們可以使用條件斷點(diǎn)!
條件斷點(diǎn)就是帶有條件表達(dá)式的斷點(diǎn),只有滿足這個(gè)條件,程序才會(huì)暫停。假想我們只想在對(duì)象處于特定狀態(tài)的時(shí)候斷點(diǎn),或者在第N次迭代循環(huán)時(shí)命中斷點(diǎn)。
點(diǎn)擊Xcode editor的‘gutter’來(lái)添加斷點(diǎn),右鍵點(diǎn)擊斷點(diǎn),然后選擇“edit breakpoint”來(lái)設(shè)置特定條件。
條件斷點(diǎn)只有在遇到特定情況時(shí)才會(huì)中斷,你可以提供給一個(gè)條件(比如i == 12),或者斷點(diǎn)應(yīng)該忽略的次數(shù)。另外,你還可以添加能根據(jù)斷點(diǎn)自動(dòng)發(fā)生的動(dòng)作,例如一個(gè)debugger command---打印一個(gè)值。
提示:添加/刪除斷點(diǎn)的鍵盤(pán)快捷鍵是command+\
另外一個(gè)重要的斷點(diǎn)技巧是添加一個(gè)異常斷點(diǎn)(exception breakpoint)。當(dāng)遇到異常時(shí), Xcode基本上都會(huì)自動(dòng)轉(zhuǎn)到main方法的autorelease pool中。
通過(guò)設(shè)置異常斷點(diǎn),你可以定位到引起異常斷點(diǎn)的具體代碼行。
如何添加異常斷點(diǎn)?
1.打開(kāi)異常斷點(diǎn)tab(command+6);2.選擇窗口左下角的”+”按鈕;3.選擇按鈕并添加‘exception breakpoint’。
這樣,當(dāng)Xcode遇到異常情況時(shí),將會(huì)在引起異常代碼的地方發(fā)生斷點(diǎn)。
從Console進(jìn)行手動(dòng)打印
理論上說(shuō),它會(huì)展示當(dāng)前環(huán)境中所有值的狀態(tài);實(shí)際上,有時(shí)候會(huì)出現(xiàn)bug,并且不會(huì)列出值或者當(dāng)你單步調(diào)試的時(shí)候不進(jìn)行更新。
一般情況下,我們?cè)赼pp代碼中添加特定斷點(diǎn),是為了通過(guò)Xcode提供的‘variables view’(該view在Xcode底部console旁邊)來(lái)查看對(duì)象的狀態(tài) 。理論上說(shuō),它可以顯示出與當(dāng)前上下文相關(guān)的所有值的狀態(tài)。實(shí)際上,有時(shí)候會(huì)有點(diǎn)小問(wèn)題,不會(huì)列出相關(guān)的值或者不會(huì)進(jìn)行相關(guān)的更新。
不過(guò),我們可以使用一些有用的console命令來(lái)檢查特定的對(duì)象。在console中輸入‘po’就可以獲得某個(gè)斷點(diǎn)的即時(shí)信息。(處理scalar值時(shí),我們可以使用‘p’)
在我們查看一個(gè)已存在的對(duì)象時(shí),這一點(diǎn)非常有用(如果對(duì)象不存在的話會(huì)打印出nil),確定對(duì)象的值,找出數(shù)組/字典運(yùn)行時(shí)的信息,甚至是比較兩個(gè)對(duì)象。因?yàn)檫@個(gè)指令打印出相關(guān)對(duì)象的內(nèi)存地址,所以你可以打印你認(rèn)為應(yīng)該一樣的兩個(gè)對(duì)象,看看它們的內(nèi)存地址是否相同。
另一個(gè)有用的,但是被隱藏的指令是recursiveDescription,你可以簡(jiǎn)單地用它對(duì)view進(jìn)行檢查。
在view中調(diào)用recursiveDescription來(lái)打印它的繼承關(guān)系。
有效的Logging
有時(shí),在調(diào)試程序的某個(gè)特定時(shí)間,我們希望將消息打印到控制臺(tái),此時(shí)‘NSLog’函數(shù)允許我們將任意輸出打印至console。
此時(shí)可以使用NSLog函數(shù),通過(guò)該函數(shù)可以將任意的輸出打印到控制臺(tái)。在不使用斷點(diǎn)時(shí),這個(gè)功能非常有用。NSLog遵從的格式與[NSString StringWithFormat]方法遵從的格式一樣。(你可以從下邊的截圖中看到)
Tip: 這里可以看到蘋(píng)果關(guān)于Objective-C中字符串格式化的信息: String Programming Guide
NSLog
NSLog非常有用,我們需要聰明地實(shí)現(xiàn)它。從NSLog打印出的任何東西都會(huì)變成代碼,任何人都可以看見(jiàn)。將設(shè)備連接到電腦,打開(kāi)XCode 中的organiser,就可以從console查看到每條日志信息,這會(huì)帶來(lái)很大的影響。想一下,你想把一些保密的算法邏輯或者用戶密碼打印到 console。正因?yàn)檫@個(gè),如果蘋(píng)果發(fā)現(xiàn)在production build中,有太多內(nèi)容輸出到console,那么你的應(yīng)用可能會(huì)遭到蘋(píng)果的拒絕。
幸運(yùn)的是,這里有一個(gè)最簡(jiǎn)單的辦法進(jìn)行l(wèi)og——通過(guò)一個(gè)宏,讓NSLog只在debug build的時(shí)候起作用。將這個(gè)功能添加到全局都能訪問(wèn)得到的頭文件中。這樣你就可以盡情的使用log了,并且當(dāng)進(jìn)行production時(shí),不會(huì)包含log相關(guān)代碼。如下代碼:
- #ifdef DEBUG
- #define DMLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
- #else
- #define DMLog(...) do { } while (0)
如果你使用DMLog,那么它只能在debug build期間打印。__PRETTY_FUNCTION__ 也可以幫忙打印出log所在的函數(shù)的名稱。
下一步
NSLog 很強(qiáng)大,但也有不少限制:
1. 只能本地打印
2. 不支持分級(jí)別的log(比如是危險(xiǎn)還是警告)
3. NSLog非常慢,大量處理時(shí)會(huì)明顯降低程序的運(yùn)行效率。
推薦兩個(gè)框架,可以避免NSLog一些限制:
• Cocoa LumberJack –眾所周知的通用的Cocoa日志框架之一,學(xué)習(xí)起來(lái)有點(diǎn)難度,但是非常強(qiáng)大。
• SNLog –NSLog的替代品。
跟蹤對(duì)象的生命周期
盡管Automatic Reference Counting (ARC)已經(jīng)讓內(nèi)存管理變得簡(jiǎn)單、省時(shí)和高效,但是在object的life-cycles中跟蹤一些重要事件依然十分重要。畢竟ARC并沒(méi)有完全排除 內(nèi)存泄露的可能性,或者試圖訪問(wèn)一個(gè)被release的對(duì)象。為了這個(gè)目的,我們可以用一些處理方法和工具來(lái)幫助我們盯著對(duì)象正在做些什么。
LOG重要事件
Objective-C 對(duì)象的 life-cycle中有兩個(gè)很重要的方法: init 和dealloc ,將這兩個(gè)方法調(diào)用的事件log到console是不錯(cuò)的選擇——你可以通過(guò)控制臺(tái)觀察到對(duì)象生命的開(kāi)始,更重要的是,可以確保對(duì)象的釋放。
- - (id)init
- {
- self = [super init];
- if (self)
- {
- NSLog(@"%@: %@", NSStringFromSelector(_cmd), self);
- }
- return self;
- }
- - (void)dealloc
- {
- NSLog(@"%@: %@", NSStringFromSelector(_cmd), self);
- }
靜態(tài)分析器和Inspector(檢查器)
Xcode中還有兩個(gè)工具可以幫我們清理代碼,減少代碼出錯(cuò)的幾率。對(duì)Xcode而言,靜態(tài)分析器工具是一個(gè)非常棒用來(lái)改善代碼的工具。比如檢 測(cè)出沒(méi)有使用過(guò)的對(duì)象,沒(méi)有release對(duì)象(針對(duì)Core Foundation對(duì)象,ARC仍然會(huì)有這樣的問(wèn)題)。通過(guò)選擇Product菜單中的‘Anlayze’可以查看到相關(guān)建議。
檢查器是非常強(qiáng)大的一組工具,通過(guò)檢查器不僅可以從不同的角度檢查程序?qū)?nèi)存的使用情況,文件系統(tǒng)的使用情況(增加、刪除、修改等),甚至還提供了自動(dòng)UI交互的方法。通過(guò)選擇Product菜單中的‘Profile’可以查看到這些檢查器。
選擇‘Profile’會(huì)打開(kāi)一個(gè)Instrument窗口,這里可以選擇一個(gè)配置模板進(jìn)行運(yùn)行。最常用的模板有zombies(稍后會(huì)討論),activity monitor和leaks。在程序運(yùn)行時(shí),對(duì)內(nèi)存泄露進(jìn)行捕捉時(shí),Leaks可能是最有用的一個(gè)模板。
Zombies是你的朋友
雖然在有ARC的地方很難再遇到讓人難受的EXC_BAD_ACCESS錯(cuò)誤了,但是在某些確定的情況下,該錯(cuò)誤還是會(huì)發(fā)生的。當(dāng)在處理 UIPopoverController或者core foundation對(duì)象時(shí),我們可以訪問(wèn)一個(gè)已經(jīng)被release掉的對(duì)象。一般,當(dāng)我們r(jià)elease內(nèi)存中的一個(gè)對(duì)象時(shí),該對(duì)象將被銷毀。但是,當(dāng) Zombies開(kāi)啟時(shí),只是將對(duì)象標(biāo)記為release,實(shí)際上該對(duì)象還停留在內(nèi)存中。當(dāng)我們?cè)L問(wèn)一個(gè)Zombie對(duì)象時(shí),Xcode可以告訴我們正在訪 問(wèn)的對(duì)象是一個(gè)不應(yīng)該存在的對(duì)象了。因?yàn)閄code知道這個(gè)對(duì)象是什么,所以可以讓我們知道這個(gè)對(duì)象在哪里,以及這是什么時(shí)候發(fā)生的。
這里有兩種方法可以查找出Zombies對(duì)象。使用檢查器中的Zombie配置模板,或者在‘Run’ build選項(xiàng)中開(kāi)啟Zombie診斷選項(xiàng)。在Stop按鈕的旁邊,點(diǎn)擊scheme名稱,然后選擇‘Edit Scheme’,點(diǎn)擊diagnostic tab項(xiàng),并勾選上‘Enable Zombie Objects’。注意,Zombie只能用在模擬器調(diào)試中,真機(jī)上不能使用。
注意,Zombie模式調(diào)試僅適用于模擬器,不能在真實(shí)設(shè)備上使用。
總結(jié)
希望以上內(nèi)容能給你幫你更高效地調(diào)試你的app,所有這些都是為了能都節(jié)省bug修復(fù)時(shí)間,這樣開(kāi)發(fā)者就能把時(shí)間花在更重要的事情上,或者打造一款偉大的應(yīng)用程序。
上邊列出的肯定不是一個(gè)全面的列表,還有很多我們沒(méi)有討論的方法,比如遠(yuǎn)程遙控bug報(bào)告,崩潰報(bào)告以及更多。也希望你能分享更多。