本文由 大俠自來也 翻譯(泰然翻譯組),,轉(zhuǎn)載請注明出處并通知泰然,。 有這樣一種情形:當我們正在快樂的致力于我們的app時,并且什么看都是無比順利,,但是突然,,坑爹啊,它崩潰了,。(悲傷地音樂響起) 我們需要做的第一件事就是:不要驚慌,。 修復崩潰不是很困難的。假如你崩潰了,,并且胡亂的改些東西,,而且還在不停的念著咒語希望bug神奇的自動消失,你大多數(shù)情況下都會使情況更麻煩,。相反的,,你需要知道一些系統(tǒng)的方法,并且學習怎么找到崩潰和他的原因,。 第一件需要知道的就是在你的代碼中準確的找到crash發(fā)生的地方:在那個文件,,那一行。Xcode debugger將會幫助你,,但是你需要懂得怎么樣最好的使用它,,這也是這篇教程展示給你的。 這篇教程對于所有的開發(fā)者都是有利的,。即使你是一個很有經(jīng)驗的ios開發(fā)者,,你也可能會從中學習到一些你不知道的小竅門,。 準備開始 下載這個例子程序。你將會看到這是一個有bug的程序,。當你打開這個項目的時候,,xcode會顯示至少8個編譯警告,這個通常都是危險的信號,。順便說一下,,我們使用xcode4.3來做這篇教程,4.2的版本也應該沒有什么問題,。 注意:為了跟隨這篇教程,,這個編譯生成的app需要運行在ios5的模擬器上面。假如你運行這個app到你的設備上,,你也會崩潰,,但是他們可能不會發(fā)生和教程一樣的情況。 在模擬器上面運行你的app,,你將會看到發(fā)生了什么,。 嘿,他崩潰了,。 有兩種最基本的crash類型常發(fā)生:SIGABRT(也叫EXC_CRASH)和EXC_BAD_ACCESS(也可能會是SIGBUS或者SIGSEGV),。 就crash而言,SIGABRT是一個比較好解決的,,因為他是一個可掌控的crash,。App會在一個目的地終止,因為系統(tǒng)意識到app做了一些他不能支持的事情,。 EXC_BAD_ACCESS是一個比較難處理的crash了,,當一個app進入一種毀壞的狀態(tài),通常是由于內(nèi)存管理問題而引起的時,,就會出現(xiàn)出現(xiàn)這樣的crash,。 幸運的是,,第一種崩潰(也是大多數(shù)崩潰)是SIGABRT,SIGABRT通常會在xcode的Debug Output窗口(在窗口的右下角)輸出一些錯誤的信息,。假如你沒有看到Debug Output窗口,在你的xcode窗口的右上角一組圖標中點擊中間那個,,假如還是沒有看到Debug Output窗口,,你需要點擊這個小窗口的右上角的中間那個圖標,,他靠近搜索框。在這個情況下,,會展示一些下面東西: Problems[14465:f803] -[UINavigationController setList:]: unrecognized selector sent to instance 0x6a33840 Problems[14465:f803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController setList:]: unrecognized selector sent to instance 0x6a33840' *** First throw call stack: (0x13ba052 0x154bd0a 0x13bbced 0x1320f00 0x1320ce2 0x29ef 0xf9d6 0x108a6 0x1f743 0x201f8 0x13aa9 0x12a4fa9 0x138e1c5 0x12f3022 0x12f190a 0x12f0db4 0x12f0ccb 0x102a7 0x11a9b 0x2792 0x2705) terminate called throwing an exception 了解這些錯誤消息是非常重要的,,因為他們包含了錯誤在那里的重要線索,一下就是需要關注的部分: [UINavigationController setList:]: unrecognized selector sent to instance 0x6a33840 “unrecognized selector sent to instance XXX” 這條錯誤消息意味著你的app正在試著執(zhí)行一個不存在的方法,。這種情況的發(fā)生,,主要是都是一個方法被錯誤的對象調(diào)用了(也就是這個對象沒有這個方法,但是你調(diào)用了他,,就錯了),。例如在這里這個問題上,對象就是UINavigationController (在內(nèi)存地址0x6a33840上),,方法就是setList:,。 知道crash的原因是很好的,但是你的第一行動目的就是指出這個錯誤的發(fā)生在代碼的那個地方,。你需要找到源文件的名字和這個錯誤方法在那一行,。你通過使用call stack(就像堆棧跟蹤(stacktrace)或者回溯(backtrace))就可以知道這些東西。 當你的程序crash了時,,在xcode窗口的左邊小窗口會啟動Debug Navigator(調(diào)試導航),。他會展示在這個app中那個線程是活動的,,并且高亮顯示crash了的線程。通常他會是線程1,,這個app的主線程,,這個線程也是你會做最多工作的線程。假如你的代碼里面使用了隊列(queues)或者后臺線程(background threads),,這個app也可能會在其他的線程里面崩潰,。
當前xocde就高亮顯示了main.m里面的main()函數(shù)。但是那些東西并沒有告訴你很多,,所以你需要繼續(xù)的向深層次的挖掘,。 為了看到堆棧的更多信息,拖拽Debug Navigator底部的滑塊到最右邊,。它將會展示出崩潰時全部的堆棧信息: 這個列表里面的每一項都是一個來這個app或者ios的framework里的方法或者函數(shù),。堆棧展示了當前活躍在這個app里面的方法或者方法。調(diào)試器(debugger)已經(jīng)暫停了這個程序,,并且所有的這些方法和函數(shù)在這個時候也被凍結(jié)了,。 在底部的函數(shù)start(),第一個被調(diào)用,。在他的執(zhí)行里面的有些地方,,,main()函數(shù)在他之前,。(Somewhere in its execution it called the function above it, main().),。他是應用程序的開始入口點,并且它經(jīng)常在底部附近,。Main()也叫UIApplicationMain()(這個針對的是ios哈,,并不是其他所有程序都是這樣的)。在這個編輯窗口里面用綠色箭頭指示的那一行(就是在這個教程最開始前面程序崩潰時停止在那個圖片上,,高亮顯示的部分),。 進一步來看看這個堆棧,UIApplication()在UIApplication對象里調(diào)用_run方法,,_run方法里面又調(diào)用CFRunLoopRunInMode()方法,,CFRunLoopRunInMode()方法里面又調(diào)用CFRunLoopSpecific()方法,就這樣一直向下調(diào)用,,一直到__pthread_kill,。 所有在這個堆棧里面的函數(shù)和方法都是灰色的,除了main()函數(shù),。那是因為他們都來自內(nèi)置的ios frameworks(ios內(nèi)置框架),。所以沒有針對他們可見的源碼。 在這個堆棧里面唯一的東西就是你有main.m的源碼,,因此xcode的代碼編輯器就顯示了它,,即使他不是這個崩潰的真正原因,。但是這個經(jīng)常混淆初學者,,但是馬上我將展示怎么樣來弄懂它,。 開個玩笑,點擊這個堆棧里面的任意一項,,你將會看到許多的匯編代碼,,這些你可能完全不理解: 加入我們得到那樣的源碼,我想很多人都會說:坑爹啊,。 異常斷點 你怎么樣找到是代碼里面的哪一行使app崩潰的,?無論什么時候,你得到的一個想這樣的堆棧路徑,,一個異常通過這個app拋出,。(你多半會說因為堆棧里面有一個函數(shù)叫objc_exception_rethrow。) 當程序由于做了一些他不能完成的事情時,,一個異常就會發(fā)生,。你所看到的就是這個異常的結(jié)果:app做了一些錯的事情,異常被拋出,,xcode展示異常的結(jié)果。理想情況下,,你想要的準確的看到異常在那里拋出的,。 幸運的是,通過使用Exception Breakpoint(異常斷點),,你可以告訴xcode在一個特定的時候暫停這個程序,。斷點是一個在特定時刻暫停你的程序的調(diào)試工具。你將會第二篇教程里面看到更多關于他們的信息,,但是現(xiàn)在你將會使用一個特殊的斷點,,它將會在拋出異常前暫停你的程序。 為了設置異常斷點,,我們不得不切換到Breakpoint Navigator(斷點導航器): 在底部有一個小的加號(“+”)按鈕,。點擊它,并且選擇Add Exception Breakpoint: 一個新的斷點將會被增加到這個列表里: 點擊Done按鈕使彈出的窗口消失,。注意在xcode工具欄上面Breakpoints button(斷點按鈕)是有效的,。加入你不想要帶著任何斷點運行你的app,你可以簡單的開關這個按鈕到off,。但是現(xiàn)在,,讓它打開,并且再一次運行這個app,。 太好了,!代碼編輯器現(xiàn)在停止并且指到了代碼中的其中一行,,不再在令人煩躁的匯編代碼了,并且注意在在左邊的的Debug Navigatot(調(diào)試導航器)里面顯示的堆棧信息也不一樣了,。 顯然的,,問題就出在AppDelegate里面的application:didFinishLaunchingWithOptions:方法里: viewController.list = [NSArray arrayWithObjects:@"One", @"Two"]; 仔細再次看看這個錯誤消息: [UINavigationController setList:]: unrecognized selector sent to instance 0x6d4ed20 在這個代碼里面,“viewController.list = something”這種方式隱式的調(diào)用了setList:方法,,也就是set方法,,因為“l(fā)ist”是MainViewController類的一個屬性。然而,,通過這個錯誤消息,,我們知道viewController這個變量沒有指向MainViewController對象,而是指向了UINavigationController,,所以顯然的,,UINavigationController沒有“l(fā)ist”屬性!所以這些變量在這里混淆了,。 打開Storyboard文件,,看看window的rootViewController屬性實際上是指向那個的: 哈哈!Storyboard的最初的view controller是一個Navigation controller,。這就是為什么window.rootViewController是一個UINavigationController對象,,而不是你自認為的MainViewController。為了修改這里,,使用下面的代碼來替代application:didFinishLaunchingWithOptions:里面的: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { UINavigationController *navController = (UINavigationController *)self.window.rootViewController; MainViewController *viewController = (MainViewController *)navController.topViewController; viewController.list = [NSArray arrayWithObjects:@"One", @"Two"]; return YES; } 通過代碼可以看出,首先你通過self.window.rootViewController得到UINavigationController,,一旦你得到了上面的,。你就可以通過請求navigation controller來得到topViewController,進而得到MainViewController?,F(xiàn)在viewController變量就是指向了正確的對象了,。 注意:一旦你得到“unrecognized selector sent to instance XXX”錯誤,你就需要檢查這個對象是不是正確類型,,并且檢查它真的是有那個名字的方法么,。你會經(jīng)常發(fā)現(xiàn)你調(diào)用一個你認為是這個對象的方法,因為指針變量可能沒有包含這個正確值,,所以導致很多的錯誤,。 另外一個經(jīng)常出現(xiàn)錯誤的原因就是將方法名稱拼寫錯誤。一會兒你將會看到一個這樣的例子,。(譯者:我個人認為有xcode的代碼提示功能,,這種錯誤應該還是比較少吧,多數(shù)應該出現(xiàn)在通過selector,,或者傳遞函數(shù)指針的時候,,應該會多點這個錯誤),。 你的第一個內(nèi)存錯誤 你可能已經(jīng)修復了你的第一個問題。再一次運行這個程序,??拥。谕瑯拥囊恍?,又崩潰了,,但是現(xiàn)在是EXC_BAD_ACCESS錯誤。那意味著這個app有內(nèi)存管理的問題,。 一個和內(nèi)存相關的崩潰一般很難定位到源代碼,,因為這個惡魔可能很早就在程序中做了壞事了。假如一段有問題的代碼混亂了內(nèi)存結(jié)構(gòu),,這樣產(chǎn)生的蝴蝶效應可能會在之后很久才表現(xiàn)出來,,并且總在不同的地方。 實際上,,在你所有的測試中,,這個bug可能永遠不會出現(xiàn),但是卻在你的客戶的設備上展露出它丑陋的腦袋,。這種是很多人都不想的,。 這種特別的崩潰但是卻很容易修復。假如你看到你的代碼編輯器,,xcode其實一直就在警告你這一行代碼,。看到左邊靠近行號的那個黃色三角形沒有,?那個指出一個編譯警告。假如你點擊那個黃色的三角形,,xcode將會彈出一個“Fix-it”的建議,,就像下面的一樣: 這個代碼使用了一系列的對象來初始化一個數(shù)組(NSArray),并且像那樣的一系列的對象應該使用nil來終止,,這個警告的標記就是想要表達一個這樣的意思,。但是代碼卻沒有那樣做,所以NSArray就很困惑,,很迷茫,。它試著讀取一個不存在的對象,最后這個app艱難的崩潰了,。 這種錯誤,,你真的不應該犯,特別是xcode已經(jīng)警告了你,。修復這個錯誤,,通過像下面一樣增加一個nil(或者你可以簡單的選擇剛剛彈出來的菜單里面“Fix-it”): viewController.list = [NSArray arrayWithObjects:@"One", @"Two", nil]; “This class is not key value coding-compliant” 重新運行這個程序,,看看為你準備的其他有趣的bug。信不信由你?它又在main.m里面崩潰了,。雖然Exception Breakpoint任然起作用了,,但是我們沒有看見任何高亮的程序代碼,這次的崩潰真的沒有發(fā)生在任何程序代碼里,。這個調(diào)用堆棧證實了這點:這里面的方法沒有一個屬于的程序的,,除了main(): 假如你從上到下瀏覽一下這些方法的名字,有些問題發(fā)生在NSObject和Key-Value Coding,。在那之下調(diào)用了[UIRuntimeOutletConnection connect],。我不知道那個是干什么的,但是看起來好像它做了連接outlet的一些事情,。在那之下的一些方法是從nib中加載view,。因此以上那些也給你一些線索。 但是,,在xcode的調(diào)試窗口,,并沒有易懂的錯誤消息。那是因為沒有異常被拋出,。在xcode告訴你異常的原因之前,,Exception Breakpoint已經(jīng)暫停了這個程序。有些時候你會從Exception Breakpoint得到一些局部的錯誤消息,,但是有些時候就得不到,。 為了得到全部的錯誤消息,點擊調(diào)試器工具欄上的“Continue Program Execution”按鈕: 你可能需要點擊好幾次才可以,,然后你將會得到錯誤消息: Problems[14961:f803] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key button.' *** First throw call stack: (0x13ba052 0x154bd0a 0x13b9f11 0x9b1032 0x922f7b 0x922eeb 0x93dd60 0x23091a 0x13bbe1a 0x1325821 0x22f46e 0xd6e2c 0xd73a9 0xd75cb 0xd6c1c 0xfd56d 0xe7d47 0xfe441 0xfe45d 0xfe4f9 0x3ed65 0x3edac 0xfbe6 0x108a6 0x1f743 0x201f8 0x13aa9 0x12a4fa9 0x138e1c5 0x12f3022 0x12f190a 0x12f0db4 0x12f0ccb 0x102a7 0x11a9b 0x2872 0x27e5) terminate called throwing an exception 就像之前的一樣,,你可以忽略下面的那些數(shù)字。他們展示了調(diào)用堆棧,,但是在調(diào)試導航器的左邊有更加直觀的堆棧調(diào)用展示,。 有趣的部分是: NSUnknowKeyException MainViewController “this class is not key value coding-compliant for the key button” 這個異常的名字為NSUnknownKeyException,它是這個錯誤很好的指示器,。它告訴你在某個地方有一個“unknown key”,。這個某一個地方通常就是MainViewController,并且這個key就是“button”,。 既然我們已經(jīng)確定了,,所有這些都是發(fā)生在裝載nib的時候。這個應用使用的是storyboard,,而不是nib文件,,但是其實storyboard內(nèi)部就是nib的集合(也就是可以有很多的nib),因此這個錯誤就在這個storyboard中。 檢查一下MainViewController的outlets: 在Connections Inspector(連接檢測器)里,,你可以看見在viewcontroller中間的UIButton是連接到MainViewController的“button”outlet上的,。因此storyboard引用了一個名叫“button”的outlet,但是通過這個錯誤消息說明它找不到這個outlet,。 讓我們來看看MainViewController.h: @interface MainViewController : UIViewController @property (nonatomic, retain) NSArray *list; @property (nonatomic, retain) IBOutlet UIButton *button; - (IBAction)buttonTapped:(id)sender; @end 這里是為這個“button”定義了外部連接屬性的(@property),,因此這個問題是什么呢?假如你仔細觀察了編譯警告的話,,你可以已經(jīng)知道是什么地方的問題了,。 假如還不知道的話,檢查一下MainViewController.m的@synthesize的內(nèi)容的話,。你現(xiàn)在看出問題沒有?。?/p> 這個代碼其實沒有@synthesize這個button的屬性,。它(@synthesize)其實是告訴MainVIewController他自己有個“button”的屬性,,提供一個后臺實例變量,并且提供getter和setter方法(這就是@synthesize所做的),。 把下面的增加到MainViewController.m里面已經(jīng)存在的@synthesize行的下面來修復這個問題: @synthesize button = _button; 現(xiàn)在這個app應該不會在你運行的時候崩潰了,! 注意:“this class is not key value coding-compliant for the key XXX”的錯誤經(jīng)常都是由于你裝載這個nib,但是里面引用的一些熟悉可能不存在,。特別是當你在代碼中移除了outlet屬性后,,但是你卻沒有在nib中移除這個連接。 Push the Button 現(xiàn)在這個app正常工作,,或者至少說啟動的時候沒有問題,。是時候來點擊這個按鈕了。 哇!這個app崩潰在main.m里面,,并且伴隨著SIGABRT,。在調(diào)試窗口打印出的錯誤消息是: Problems[6579:f803] -[MainViewController buttonTapped]: unrecognized selector sent to instance 0x6e44850 堆棧跟蹤也不是很有啟發(fā)。只是列出了一些和一個方法相關的或者發(fā)送了事件并且執(zhí)行了動作的方法,,但是你已經(jīng)知道到了被涉及的動作了,。畢竟,你點擊了一個按鈕,,這個按鈕的IBAction方法應該被調(diào)用。 當然你之前應該已經(jīng)看過了這個錯誤消息,。一個被調(diào)用的方法不存在,。這個時候目標對象應該是MainViewController,由于動作方法經(jīng)常存在于一個包含按鈕的view controller里面,,所以這個看起來是正確,。并且你看MainViewController.h文件,這個IBAction方法確實在里面: - (IBAction)buttonTapped:(id)sender; 是這樣的嗎,?錯誤消息顯示這個方法的名字是buttonTapped,,但是MainViewController的方法卻是buttonTapped:(注意冒號),由于這個方法需要接受一個參數(shù)(名字是sender),,所以在方法名字后面有個冒號。從這個錯誤消息看出,,這個方法沒有冒號,,因此不需要參數(shù)。所以這個方法看其實應該是這樣的: - (IBAction)buttonTapped; 這個里發(fā)生了什么,?這個方法最初的時候不需要參數(shù)(有些時候這樣動作方法是被允許的),,并且在那個時候,他為這個按鈕在storyboard里面連接了Touch Up Inside的時間方法,。然而,,在那之后某個時候,這個方法的形式被修改為包含了一個“sender”參數(shù),,但是,,卻沒有去更新storyboard。 你可以在storyboard里面看看,,在這個按鈕的連接檢測器: 第一,,斷開Touch Up Inside 事件(點擊這個小“X”),然后再次連接它到MainViewController里,,但是這次選擇這個buttonTapped:方法,。注意在連接檢查器里面看看這個方法后面是有一個冒號的。 運行這個app,,再一次點擊按鈕,。我們又得到了這個“unrecognized selector”消息,但是這次他正確的定位到了buttonTapped:方法里面,。 Problems[6675:f803] -[MainViewController buttonTapped:]: unrecognized selector sent to instance 0x6b6c7f0 假如你仔細看的話,,編譯器警告應該又給你指出解決方案。Xcode提出MainViewController的實現(xiàn)是不完整的,。特別的,,buttonTapped:方法沒有被發(fā)現(xiàn)。 是時候看看MainViewController.m了,,在這里確實是有buttonTapped:方法啊……………..等等,,拼寫錯誤了: - (void)butonTapped:(id)sender 很簡單的修改,重命名這個方法: - (void)buttonTapped:(id)sender 提示:你沒必要聲明這個方法為IBAction,,假如你覺得這樣是很優(yōu)雅的,,你可以這樣做。 注意:假如你仔細注意到這些編譯器警告的話,,這些問題很容易看出來的,。就個人而言,我把所有的警告當成嚴重的錯誤(在xcode里面的編譯設置(Build Settings)里面可以設置警告作為錯誤提示的),在運行程序以前,,我會修改所有的,。Xcode在指出愚蠢的錯誤表現(xiàn)的相當好,就像這里這樣,,并且注意到這些提示是很明智的,。 Messing with Memory(混亂內(nèi)存) 經(jīng)過了這么多,你知道崩潰一直在繼續(xù)從未停止過,。運行這個app,,點擊按鈕,然后等待崩潰,。好,,現(xiàn)在就來了: 這里是另一種EXC_BAD_ACCESS崩潰。幸運的是,,xcode已經(jīng)準確給你指示出位置在那里了,,在這個buttonTapped:方法里面: NSLog("You tapped on: %s", sender); 有些時候,你可能在上面花費一些時間才會反應過來,,但是xcode提供了幫助,,僅僅需要點擊這個黃色的三角形來看這個錯誤是什么: NSLog呈現(xiàn)一個Objective-c類型的字符串,而不是一個c字符串,,因此插入一個@符號來修改它: NSLog(@"You tapped on: %s", sender); 你將會注意到這個警告的黃色三角形依然沒有消失,。這是因為在這行還有另外一個bug,這個bug可能會或者可能不會使你的程序崩潰,。有些時候這個代碼工作很好,,或者現(xiàn)在看起來很好,但是有些時候他就會崩潰,。(特別是有些時候只在你的客戶的設備上面,,絕不會在你的設備上),。 讓我們來看看這個新的警告: 這個“%s”專門為c語言類型的字符串。一個c類型的字符串就是把這個內(nèi)存分成片段(一個老式的字節(jié)數(shù)組),,通過一個所謂的”NUL character”(其實就是一個為0的值)來終止,。例如這個“Crash,!”看起來就是這樣的: 無論是什么時候,,你使用一個函數(shù)或者方法來操作這個c類型的字符串,,你不得不確定這個字符串是以一個0值來結(jié)尾的,否則這個函數(shù)將不知道這個字符串已經(jīng)結(jié)束了,。 現(xiàn)在來看看,,當你指定了在NSlog()中用“%s”來格式化字符串,或者在NSString 的stringWithFormat里面,,這個變量將會被當做是一個c類型的字符串。假如這個“sender”指向一個包含0字節(jié)的內(nèi)存,這個NSlog()將不會崩潰,,但是輸出的東西就會像這樣: You tapped on: x?j 再一次運行這個app,,點擊這個按鈕,等待它崩潰?,F(xiàn)在在Debug窗口的左邊部分,,右擊“sender”,并且選擇“view Memory of “*sender””選項(確保是選擇的是帶有星號的sender),。 Xcode將會展示出這個內(nèi)存地址的內(nèi)容,,恰恰這個就是NSlog()打印出來的內(nèi)容。 然而,,這里并不能保證這里有空位(結(jié)束標志位),,所以你完全很容易執(zhí)行到一個EXC_BAD_ACCESS的錯誤。 假如你經(jīng)常在模擬器上面測試的話,,這個很長時間都可能不會發(fā)生,,然而這種情況一般都是在很特殊的情況環(huán)境下就可能發(fā)生。所以這種類型的bug很難跟蹤,。 當然,,在這種情況下,xcode已經(jīng)警告你這個錯誤的格式化字符串,,因此這個特別的bug是很容易發(fā)現(xiàn)的,。但是無論什么時候,你使用c類型的字符串或者手動直接操作內(nèi)存的,,都應該非常的小心的不要混亂了其他的內(nèi)存,。 假如你非常的幸運,這個app將會經(jīng)常崩潰,,這個bug很容易找到,,但是通常情況是這個app會崩潰在某個時候,而且這個問題很難重現(xiàn)!之后尋找這個bug將會是一個史詩般的工程,,十分麻煩,。 修復這個NSLog()的形式,就像下面的一樣: NSLog(@"You tapped on: %@", sender); 運行這個app,,并且再一次點擊這個按鈕?,F(xiàn)在這個NSLog()做了他能做的了,并且看起來好像不會崩潰在buttonTapped:這個函數(shù)里面了,。 和調(diào)試器交朋友(Making Friends With the Debugger) 看看這最近的崩潰,,xcode指示到了這一行: [self performSegueWithIdentifier:@"ModalSegue" sender:sender]; 在Debug窗口里面沒有消息打印出來。你可以點擊這個繼續(xù)執(zhí)行這個程序的按鈕,,就像前面一樣,,但是你也可以在調(diào)試器里面輸入一個命令來得到這個錯誤消息,。這樣做的好處就是,這個app可以保持暫停在這個同樣的地方,。 假如你準備在模擬器里面運行這個,,你可以在“(lldb)”提示的后面輸入下面的: (lldb) po $eax LLDB在xcode4.3或者之后的版本里面是默認的調(diào)試器。假如你正在使用老一點版本的xcode的話,,你又GDB調(diào)試器,。他們有一些基本的相同的命令,因此假如你的xcode使用的是“(gdb)”提示,,而不是“(lldb)”提示的話,,你也能夠更隨一起做,而沒有問題,。 “po”命令是“print object”(打印對象)的簡寫,。“$eax”是cup的一個寄存器,。在一個異常的情況下,,這個寄存器將會包含一個異常對象的指針。注意:$eax只會在模擬器里面工作,,假如你在設備上調(diào)試,,你將需要使用”$r0″寄存器。 例如,,假如你輸入: (lldb) po [$eax class] 你將會看像這樣的東西: (id) $2 = 0x01446e84 NSException 這些數(shù)字不重要,,但是很明顯的是你正在處理的NSException對象在這里。 你可以對這個對象調(diào)用任何方法,。例如: (lldb) po [$eax name] 這個將會輸出這個異常的名字,,在這里是NSInvalidArgumentException,并且: (lldb) po [$eax reason] 這個將會輸出錯誤消息: (unsigned int) $4 = 114784400 Receiver () has no segue with identifier 'ModalSegue' 注意:當你僅僅使用了“po $eax”,,這個命令將會對這個對象調(diào)用“description”方法和打印出來,,在這個情況下,你也會得到錯誤的消息,。 因此解釋下那是什么情況:你正在嘗試執(zhí)行一個名叫“ModalSegue”的segue,,但是很顯然,在MainViewController里面并沒有這樣的的segue,。 Storyboard展示出來這個segue是存在的,,但是你忘記了設置它的標示,一個典型的錯誤: 改變這個segue的標示為“ModalSegue”,。再一次運行這個app,,等待一下,點擊這個按鈕 ,,這個時候不再有crash了,!但是這里只是我們下個部分的開端——-tableview不應該是空的,! 何去何從?
在第二部分的教程里面,,我們將會遇到更多的bug,。并且學習到更多關于調(diào)試的工具,包括NSLog()陳述,,斷點和僵尸對象。 |
|