Xcode開(kāi)發(fā)調(diào)試技巧總結(jié)
一、概述
1.掌握調(diào)試技巧,調(diào)試技術(shù)
最基本,最重要的調(diào)試手段包括:單步跟蹤,斷點(diǎn),變量觀察等。
單步跟蹤(Step)所謂單步跟蹤是指一行一行地執(zhí)行程序,每執(zhí)行一行語(yǔ)句后就停下來(lái)等待指示,這樣你就能夠仔細(xì)了解程序的執(zhí)行順序,以及當(dāng)時(shí)的各種狀況。
斷點(diǎn)(Breakpoint)斷點(diǎn)是調(diào)試中非常重要的一個(gè)手段。由于在執(zhí)行到某些代碼前需要執(zhí)行許多其它代碼,不可能用單步跟蹤一條一條執(zhí)行過(guò)來(lái),這時(shí)只要在需要暫停的地方設(shè)置一個(gè)斷點(diǎn),然后讓程序運(yùn)行,當(dāng)執(zhí)行到這個(gè)斷點(diǎn)位置時(shí)不需要用戶(hù)干預(yù)就會(huì)暫停并返回集成調(diào)試程序.斷點(diǎn)必須位于可執(zhí)行代碼行上,凡設(shè)置在注釋,空白行,變量說(shuō)明上的都是無(wú)效的。另外,斷點(diǎn)既可以在設(shè)計(jì)狀態(tài)下設(shè)置也可以在運(yùn)行調(diào)試狀態(tài)下設(shè)置。根據(jù)斷點(diǎn)調(diào)試找到錯(cuò)誤處。在程序開(kāi)發(fā)中,為了找到程序的bug,通常采用的一種調(diào)試手段,一步一步跟蹤程序執(zhí)行的流程,根據(jù)變量的值,找到錯(cuò)誤的原因。 在需要調(diào)試的代碼斷設(shè)置斷點(diǎn),然后按預(yù)設(shè)的快捷鍵步進(jìn)。調(diào)試狀態(tài)運(yùn)行程序,程序執(zhí)行到有斷點(diǎn)的地方會(huì)停下來(lái)。
2.內(nèi)存泄漏解釋
簡(jiǎn)單的說(shuō)就是申請(qǐng)了一塊內(nèi)存空間,使用完畢后沒(méi)有釋放掉。它的一般表現(xiàn)方式是程序運(yùn)行時(shí)間越長(zhǎng),占用內(nèi)存越多,最終用盡全部?jī)?nèi)存,整個(gè)系統(tǒng)崩潰。由程序申請(qǐng)的一塊內(nèi)存,且沒(méi)有任何一個(gè)指針指向它,那么這塊內(nèi)存就泄露了。 一般我們常說(shuō)的內(nèi)存泄漏是指堆內(nèi)存的泄漏。堆內(nèi)存是指程序從堆中分配的,大小任意的(內(nèi)存塊的大小可以在程序運(yùn)行期決定),使用完后必須顯示釋放的內(nèi)存。應(yīng)用程序一般使用malloc,realloc,new等函數(shù)從堆中分配到一塊內(nèi)存,使用完后,程序必須負(fù)責(zé)相應(yīng)的調(diào)用free或delete釋放該內(nèi)存塊,否則,這塊內(nèi)存就不能被再次使用,我們就說(shuō)這塊內(nèi)存泄漏了。
使用leaks工具幫助查看內(nèi)存泄漏問(wèn)題.在的XCode工具列,Run=>“Run with Perfromance Tool=>Leak 在iPhone程式開(kāi)發(fā)中,使用NSLog直接在控制臺(tái)印出retainCount也是一個(gè)檢視內(nèi)存泄漏的方法,但是的XCode提供了更方便的泄漏工具供開(kāi)發(fā)者使用。 http://blog.csdn.net/cloudhsu/archive/2010/07/22/5754818.aspx (重要)
二、常見(jiàn)錯(cuò)誤
1.Objective-C EXC_BAD_ACCESS
程序遇到 Bug 并不可怕,大部分的問(wèn)題,通過(guò)簡(jiǎn)單的 Log 或者 代碼分析并不難找到原因所在。但是在 Objective-C 編程中遇到 EXC_BAD_ACCESS 問(wèn)題的時(shí)候,通過(guò)簡(jiǎn)單常規(guī)的手段很難發(fā)現(xiàn)問(wèn)題。這篇文章,給大家介紹一個(gè)常用的查找 EXC_BAD_ACCESS 問(wèn)題根源的方法。首先說(shuō)一下 EXC_BAD_ACCESS 這個(gè)錯(cuò)誤,可以這么說(shuō),90%的錯(cuò)誤來(lái)源在于對(duì)一個(gè)已經(jīng)釋放的對(duì)象進(jìn)行release操作。 舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明吧,首先看一段Java代碼:
- public class Test{
- public static void main(String[] args){
- String s = “This is a test string”;
- s = s.substring(s.indexOf(“a”),(s.length()));
- System.out.println(s);
- }
- }
這種寫(xiě)法在Java中很常見(jiàn)也很普遍,這不會(huì)產(chǎn)生任何問(wèn)題。但是到了 Objective-C 中,就會(huì)出事,考慮這個(gè)程序:
- #import <Foundation/Foundation.h>
- int main (int argc, c*****t char * argv[])
- { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- NSString* s = [[NSString alloc]initWithString:@”This is a test string”];
- s = [s substringFromIndex:[s rangeOfString:@"a"].location];//內(nèi)存泄露
- [s release];//錯(cuò)誤釋放 [pool drain];//EXC_BAD_ACCESS return 0;
- }
這個(gè)例子當(dāng)然狠容易的看出問(wèn)題所在,如果這段代碼包含在一個(gè)很大的邏輯中,確實(shí)容易被忽略。Objective-C 這段代碼有三個(gè)致命問(wèn)題:1,內(nèi)存泄露。2,錯(cuò)誤釋放。3,造成 EXC_BAD_ACCESS 錯(cuò)誤。
1,內(nèi)存泄露。 NSString* s = [[NSString alloc]initWithString:@”This is a test string”]; 創(chuàng)建了一個(gè) NSString Object, 隨后的 s = [s substringFromIndex:[s rangeOfString:@"a"].location]; 執(zhí)行后,導(dǎo)致創(chuàng)建的對(duì)象引用消失,直接造成內(nèi)存泄露。
2,錯(cuò)誤釋放。[s release]; 這個(gè)問(wèn)題,原因之一是一個(gè)邏輯錯(cuò)誤,以為 s 還是我們最初創(chuàng)建的那個(gè) NSString 對(duì)象。 第二是因?yàn)閺? substringFromIndex:(NSUInteger i) 這個(gè)方法返回的 NSString 對(duì)象,并不需要我們來(lái)釋放, 它其實(shí)是一個(gè)被 substringFromIndex 方法標(biāo)記為 autorelease 的對(duì)象。如果我們強(qiáng)行的釋放了它,那么會(huì)造成 EXC_BAD_ACCESS 問(wèn)題。
3,造成 EXC_BAD_ACCESS 錯(cuò)誤。EXC_BAD_ACCESS。由于 s 指向的 NSString 對(duì)象被標(biāo)記為 autorelease, 則在 NSAutoreleasePool 中已有記錄。但是由于我們?cè)谇懊驽e(cuò)誤的釋放了該對(duì)象,則當(dāng) [pool drain] 的時(shí)候,NSAutoreleasePool 又一次的對(duì)它記錄的 s 對(duì)象調(diào)用了 release 方法,但這個(gè)時(shí)候 s 已經(jīng)被釋放不復(fù)存在,則 直接導(dǎo)致了 EXC_BAD_ACCESS問(wèn)題。
那么,知道了 EXC_BAD_ACCESS 的誘因之一后,如何快速高效的定位問(wèn)題? 1: 為工程運(yùn)行時(shí)加入 NSZombieEnabled 環(huán)境變量,并設(shè)為啟用,則在 EXC_BAD_ACCESS 發(fā)生時(shí),XCode 的 C*****ole 會(huì)打印出問(wèn)題描述。 首先雙擊 XCode 工程中,Executables 下的 可執(zhí)行模組, 在彈出窗口中,Variables to be set in the environment,添加 NSZombieEnabled,并設(shè)定為 YES,點(diǎn)擊選中復(fù)選框啟用此變量。 這樣,運(yùn)行上述 Objective-C 時(shí)會(huì)看到控制臺(tái)輸出: Untitled[3646:a0f] *** -[CFString release]: message sent to deallocated instance 0x10010d340 這條消息對(duì)于定位問(wèn)題有很好的提示作用。但是很多時(shí)候,只有這條提示是不夠的,我們需要更多的提示來(lái)幫助定位問(wèn)題,這時(shí)候再加入 MallocStackLogging 來(lái)啟用malloc記錄。 當(dāng)錯(cuò)誤發(fā)生后,在終端執(zhí)行: malloc_history ${App_PID} ${Object_instance_addr} 則會(huì)獲得相應(yīng)的 malloc 歷史記錄,比如對(duì)于上一個(gè)控制臺(tái)輸出 Untitled[3646:a0f] *** -[CFString release]: message sent to deallocated instance 0x10010d340 則我們可以在終端執(zhí)行 結(jié)果如下:
Buick-Wongs-MacBook-Pro:Downloads buick$ malloc_history 3646 0x10010d340 malloc_history Report Version: 2.0 Process: Untitled [3646] Path: /Users/buick/Desktop/Untitled/build/Debug/Untitled Load Address: 0×100000000 Identifier: Untitled Version: ??? (???) Code Type: X86-64 (Native) Parent Process: gdb-i386-apple-darwin [3638] Date/Time: 2011-02-01 15:07:04.181 +0800 OS Version: Mac OS X 10.6.6 (10J567) Report Version: 6 ALLOC 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | +[NSString initialize] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | NXCreateMapTableFromZone | malloc_zone_malloc —- FREE 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | _finishInitializing | free ALLOC 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | -[NSPlaceholderString initWithString:] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | _class_initialize | +[NSMutableString initialize] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | NXCreateMapTableFromZone | malloc_zone_malloc —- FREE 0x10010d340-0x10010d357 : thread_7fff70118ca0 |start | main | -[NSPlaceholderString initWithString:] | objc_msgSend | lookUpMethod | prepareForMethodLookup | _class_initialize | _class_initialize | _finishInitializing | free ALLOC 0x10010d340-0x10010d35f : thread_7fff70118ca0 |start | main | -[NSCFString substringWithRange:] | CFStringCreateWithSubstring | __CFStringCreateImmutableFunnel3 | _CFRuntimeCreateInstance | malloc_zone_malloc 這樣就可以很快的定位出問(wèn)題的代碼片段了,注意輸出的***一行,這行雖然不是問(wèn)題的最終原因,但是離問(wèn)題點(diǎn)已經(jīng)很近了,隨著它找下去,八成就會(huì)找到問(wèn)題。 當(dāng)然,EXC_BAD_ACCESS 的定位方法還有很多,隨著具體問(wèn)題的不同而不同。
2. _OBJC_CLASS_$_ errors 造成這個(gè)錯(cuò)誤,存在兩種原因: 1.項(xiàng)目未添加一個(gè) CoreData framework; 2.由于某個(gè)或某幾個(gè).m文件沒(méi)有被標(biāo)記(打鉤)的原因造成的; 我的錯(cuò)誤原因是第二種原因造成的,我的解決方法是將與服務(wù)器端沖突的幾個(gè)先在項(xiàng)目中刪除,然后再將刪除的文件重新拖拽到xcode中, 注意在添加文件是要記得打鉤。 如果再次運(yùn)行發(fā)現(xiàn)Failed to upload *.app問(wèn)題,首先請(qǐng)先關(guān)閉xcode,然后將項(xiàng)目的build目錄刪除,再次重新打開(kāi)xcode,運(yùn)行程序,問(wèn)題解決。
3.下面的錯(cuò)誤:主要是在程序中加了中文符號(hào) ; 而不是在英文下 ; 引起的錯(cuò)誤 在編寫(xiě)程序時(shí),需要注意 一定要在 英文下編寫(xiě)。
- link.c:69: error: stray ‘\357’ in program
- link.c:69: error: stray ‘\274’ in program
- link.c:69: error: stray ‘\233’ in program
- link.c:70: error: expected ‘;’ before ‘insert_elem’
- link.c:70: error: stray ‘\357’ in program
- link.c:70: error: stray ‘\274’ in program
- link.c:70: error: stray ‘\233’ in program
三、iPhone 開(kāi)發(fā)經(jīng)驗(yàn)教訓(xùn)總結(jié)參考
- 所有的UI操作,都要切換到主線程中進(jìn)行.否則,會(huì)發(fā)生莫名其妙的錯(cuò)誤.在主線程中,runloop默認(rèn)是開(kāi)啟狀態(tài)的。非主線程中,如果要用到 runloop,必須手動(dòng)開(kāi)啟runloop。關(guān)于runloop知識(shí)。對(duì)于常見(jiàn)的 EXEC_BAD_ACCESS,EXC_BAD_INSTRUCTION,錯(cuò)誤,一般都是因?yàn)樵L問(wèn)已經(jīng)被release的對(duì)象造成的。尤其是在一個(gè)線程 中訪問(wèn)另外一個(gè)線程的autorelease庫(kù)中的對(duì)象,尤其要注意此類(lèi)問(wèn)題。嚴(yán)格遵守iphone 內(nèi)存管理手冊(cè),對(duì)于不是由你創(chuàng)建的對(duì)象,不要越權(quán)release,否則,可能會(huì)導(dǎo)致程序crash.有時(shí),一些看起來(lái)非常嚴(yán)重的bug,在經(jīng)過(guò)N過(guò)次努 力,多種思路嘗試fix之后,再回頭分析bug產(chǎn)生的原因,你會(huì)發(fā)現(xiàn),造成這個(gè)嚴(yán)重bug的原因,很可能是你違反了一個(gè)眾所周知的規(guī)則引起的.這個(gè)規(guī)則你 非常清楚,熟悉,但就是在coding的時(shí)候,稍不留神違反了它.于是就帶來(lái)了災(zāi)難性后果.除了面向?qū)ο蟮腸ocoa外,iphone編程不要忘記非面向 對(duì)象的Core Foundation。 面向?qū)ο髱?kù)里很多沒(méi)有的功能,可以嘗試在Core Foundation里找找。比如:RSA算法,MD5算法,SHA1算法,AES加密算法等,cocoa對(duì)象庫(kù)里并沒(méi)有相應(yīng)的實(shí)現(xiàn),但在core foundation里,均有相應(yīng)的實(shí)現(xiàn)。NSString類(lèi)里沒(méi)有的字符串編碼GBK,GB2312,GB18030等,在 CoreFoundation里,能找到相應(yīng)的編碼。建立socket連接,獲得輸入流和輸出流時(shí),也需要使用Core Foundation里的CFNetwork api。等等。通過(guò)設(shè)置NSZombieEnabled參數(shù),有非常有效幫助解決內(nèi)存釋放錯(cuò)誤。在消除某個(gè)對(duì)象時(shí),如果為該對(duì)象設(shè)置了delegate, 則需要先將delegate設(shè)成nil,這是一種良好的代碼習(xí)慣。
- 在3.0 的Simulator上使用Instruments 檢測(cè)內(nèi)存泄漏時(shí),無(wú)法看到函數(shù)名,只能看到一些地址指針.在3.1,3.1.2,3.1.3的simulator都正常,能夠正常地看到是在哪個(gè)函數(shù)中存 在的內(nèi)存泄漏.通過(guò)Nib文件加載viewcontroller的各種UI控件時(shí)時(shí),在viewDidLoad函數(shù)里,viewController的控 件才能使用。在viewcontroller的構(gòu)造函數(shù)里,nib里的控件都還沒(méi)有完成鏈接構(gòu)造呢。 iPhone程序崩潰不要著急??梢越Y(jié)合使用C*****ole和objc_exception_throw可以快速定位根源所在。在CFNetwork 中,有時(shí)候使用CFWriteStreamWrite方法寫(xiě)數(shù)據(jù)時(shí),會(huì)導(dǎo)致該現(xiàn)成被長(zhǎng)久block住。
- 原因:在 CFWriteStream不能接受數(shù)據(jù)時(shí),寫(xiě)數(shù)據(jù)了。具體解決辦法:在CFSriteStream收到異步的 kCFStreamEventCanAcceptBytes通知時(shí),再開(kāi)始寫(xiě)數(shù)據(jù)。此時(shí)可避免CFWriteStreamWrite導(dǎo)致線程被block 的情形。使用Eavesdrop 抓取網(wǎng)絡(luò)數(shù)據(jù)包。
- 在Iphone上有兩種讀取圖片數(shù)據(jù)的簡(jiǎn)單方法: UIImageJPEGRepresentation和UIImagePNGRepresentation. UIImageJPEGRepresentation函數(shù)需要兩個(gè)參數(shù):圖片的引用和壓縮系數(shù).而UIImagePNGRepresentation只需 要圖片引用作為參數(shù).通過(guò)在實(shí)際使用過(guò)程中,比較發(fā)現(xiàn): UIImagePNGRepresentation(UIImage* image) 要比UIImageJPEGRepresentation(UIImage* image, 1.0) 返回的圖片數(shù)據(jù)量大很多.譬如,同樣是讀取攝像頭拍攝的同樣景色的照片, UIImagePNGRepresentation()返回的數(shù)據(jù)量大小為199K ,而 UIImageJPEGRepresentation(UIImage* image, 1.0)返回的數(shù)據(jù)量大小只為140KB,比前者少了50多KB.如果對(duì)圖片的清晰度要求不高,還可以通過(guò)設(shè)置 UIImageJPEGRepresentation函數(shù)的第二個(gè)參數(shù),大幅度降低圖片數(shù)據(jù)量.譬如,剛才拍攝的圖片, 通過(guò)調(diào)用UIImageJPEGRepresentation(UIImage* image, 1.0)讀取數(shù)據(jù)時(shí),返回的數(shù)據(jù)大小為140KB,但更改壓縮系數(shù)后,通過(guò)調(diào)用UIImageJPEGRepresentation(UIImage* image, 0.5)讀取數(shù)據(jù)時(shí),返回的數(shù)據(jù)大小只有11KB多,大大壓縮了圖片的數(shù)據(jù)量 ,而且從視角角度看,圖片的質(zhì)量并沒(méi)有明顯的降低.因此,在讀取圖片數(shù)據(jù)內(nèi)容時(shí),建議優(yōu)先使用UIImageJPEGRepresentation,并可 根據(jù)自己的實(shí)際使用場(chǎng)景,設(shè)置壓縮系數(shù),進(jìn)一步降低圖片數(shù)據(jù)量大小.