轉自:http://www.ityran.com/archives/1143
------------------------------------------------
歡迎回到當程序崩潰的時候怎么辦 教程!
在這個教程的第一部分,我們介紹了SIGABRT和EXC_BAD_ACCESS錯誤,並且舉例說明了一些使用xcode調試器(Xcode debugger)和異常斷點(Exception Breakpoints)解決問題的策略。
但是我們的app仍然有一些問題!就像我們看到的,他工作的並不是很好,並且這里仍然有許多潛在的可能崩潰的問題。
幸運的是,在這個教程的第二部分,也是最后一部分,我們可以學習更多的技術來處理這些問題。
所以我們就不在啰嗦了,讓我們回到繼續修正這個充滿bug的app中吧!
Getting Started: When What’s Supposed to Happen, Doesn’t
在第一部分我們停止的地方,經過許多的調試工作之后,我們運行這個程序他是不會崩潰的。但是他卻展現了一個沒有預料到的空的table,就像下面一樣:
當你覺得一些事情應該發生,但是卻沒有發生的時候,這里有些你可以使用一些技巧來排除問題。在這個教程里面,我們首先是學習使用NSlog來解決這個問題。
這個table view controller的類是ListViewController。在一系列的任務執行之后,這個app應該裝載ListViewController,並且在屏幕上面顯示出來。你可以做一個測試,來確定view controller的方法是執行了的。所以viewDidLoad這個方法看起來應該是一個好地方來做測試。
在ListViewController.m,增加一個NSLog()到viewDidload,就像下面一樣:
- -(void)viewDidLoad
- {
- [super viewDidLoad];
- NSLog(@"viewDidLoad is called");
- }
當你運行這個app時,你應該期望當我們點擊了“Tap Me”按鈕后在調試窗口看到“viewDidLoad is called”這樣文字。現在就來試試,點都不驚訝,在調試窗口什么也沒有出現。那就意味着ListViewController類根本沒有被使用!
這個多半意味着,你可能忘記了告訴storyboard你想要為table view controller場景使用ListViewController類。
由上圖我們可以看出,在身份檢查器(Identity Inspector)的類屬性區域是設置的默認值UITableViewController。改變這個Custom Class下面的class為ListViewController,然后再一次運行這個app。現在在調試窗口應該就會出現“viewDidLoad is called”文字:
- PProblems[18375:f803]You tapped on:<UIRoundedRectButton:0x6894800; frame =(119189;8237);
- opaque = NO; autoresize = RM+BM; layer =<CALayer:0x68948f0>>
- Problems[18375:f803] viewDidLoad is called
但是這次app將會再一次崩潰,但是卻是一個新的問題。
注意:一旦你的代碼好像沒起什么什么作用的話,放置一些NSLog()在確切的地方,來看看是否這個方法是被執行了的和cpu通過怎么樣路徑執行這個方法。使用NSLog()來測試你假設將會執行的代碼。
Assertion Failures
這個新的有趣的崩潰。它是一個SIGABRT,並且在調試窗口打印出來的是以下消息:
- Problems[18375:f803]***Assertion failure in-[UITableView _createPreparedCellForGlobalRow:
- withIndexPath:],/SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:6072
我們得到的是一個執行UITableView的一些方法的一個“斷言錯誤(assertion failure)”。當某些東西出錯了之后,一個斷言是一個內部相容性的檢查器,並且會拋出一個異常。你也可以放置斷言在你的代碼里。例如:
- -(void)doSomethingWithAString:(NSString*)theString
- {
- NSAssert(string!=nil,@"String cannot be nil");
- NSAssert([string length]>=3,@"String is too short");
- ...
- }
在上面的方法里面,我們讓一個NSString對象作為這個函數的變量,但是代碼卻不允許調用者傳遞一個nil或者長度小於3的字符串。假如這些條件中的一個不匹配的話,這個app將會終止,並且拋出一個異常。
你可以使用斷言來作為一個防御性編程技術,因此你應該確定這個就是我們想要的代碼行為。斷言通常只在調試編譯下有用的,因此他們對發布到app store的最終的app是沒有運行時的影響的。
在這個情況下,某些情況觸發了一個UITableView的斷言錯誤,但是你並沒有完全確定在那個地方。App也是停止在main.m里面,並且在執行堆棧里面只包含了框架(framework)的方法。
從這些方法的名字,我們可以猜測這個錯誤發生在重畫這個tableview的某些地方。例如,我們可以看到layoutSubviews和_updateVisibleCellsNow:這些名字的方法。
繼續運行這個app來看看是否可以得到一些比較好的錯誤消息—–記住,現在只是在拋出異常的時候暫停了程序,並沒有崩潰。點擊繼續程序按鈕,或者在調試窗口鍵入下面的命令:
- (lldb) c
你可能不得不多點擊幾次繼續按鈕,“c”命令也是一個簡短的繼續指令,和點擊繼續按鈕一個效果,並不是就直接執行到最后。
現在這個調試窗口噴發出一些比較有用的信息:
- ***Terminating app due to uncaught exception 'NSInternalInconsistencyException',
- reason:'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
- ***Firstthrow call stack:
- (0x13ba0520x154bd0a0x1362a780x99a2db0xaaee30xab5890x96dfd0xa58510x50301
- 0x13bbe720x1d6492d0x1d6e8270x1cf4fa70x1cf6ea60x1cf65800x138e9ce0x1325670
- 0x12f14f60x12f0db40x12f0ccb0x12a38790x12a393e0x11a9b0x27220x2695)
- terminate called throwing an exception
太好了,這是一個相當好的一個線索。顯然這個UITableView的數據源沒有從tableView:cellForRowAtIndexPath:方法返回一個有效的cell,因此在ListViewController.m方法里面增加一些調試輸出信息來看看:
- -(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
- {
- staticNSString*CellIdentifier=@"Cell";
- UITableViewCell*cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- NSLog(@"the cell is %@", cell);
- cell.textLabel.text =[list objectAtIndex:indexPath.row];
- return cell;
- }
你增加一個NSLog()標記。再一次運行這個app,看看輸出了什么:
- Problems[18420:f803] the cell is(null)
從以上信息我們可以看出,調用dequeueReusableCellwithIdentifier:返回的卻是nil,這就意味着使用“Cell”作為標識符的cell可能不存在(因為這個app使用的是標准的cell的storyboard)。
當然,這也是愚蠢的bug,並且毫無疑問的是,在以前解決這個需要很長的時間,但是現在卻不是了,因為xcode已經通過靜態編譯警告了你:“Prototype cells must have reuse identities。(標准的cell必須有重用的標識)”。這個是不能忽視的警告:
打開storyboard,選擇這個標准的cell(在tableview的頂端,並且顯示的是“Title”的單獨的一個cell),並且設置cell的標識符為“Cell”:
將那個修復了之后,所以的編譯警告應該沒有了。運行這個app,現在這個調試窗口應該會打印出來:
- Problems[7880:f803] the cell is<UITableViewCell:0x6a6d120; frame =(00;32044); text ='Title'; layer =<CALayer:0x6a6d240>>
- Problems[7880:f803] the cell is<UITableViewCell:0x6877620; frame =(00;32044); text ='Title'; layer =<CALayer:0x6867140>>
- Problems[7880:f803] the cell is<UITableViewCell:0x6da1e80; frame =(00;32044); text ='Title'; layer =<CALayer:0x6d9fae0>>
- Problems[7880:f803] the cell is<UITableViewCell:0x6878c40; frame =(00;32044); text ='Title'; layer =<CALayer:0x6878f60>>
- Problems[7880:f803] the cell is<UITableViewCell:0x6da10c0; frame =(00;32044); text ='Title'; layer =<CALayer:0x6d9f240>>
- Problems[7880:f803] the cell is<UITableViewCell:0x6879640; frame =(00;32044); text ='Title'; layer =<CALayer:0x6878380>>
Verify Your Assumptions
你的NSLog()打印出來的消息,已經告訴我們6個table view cell被創建了,但是在table上面什么都看不見。怎么回事呢?假如你在模擬器里面到處點擊一下,你將會注意到tableview中6個cell中的第一個卻能夠被選中。所以,顯然cells都是存在的,只是他們都是空的:
是時候需要更多的調試記錄了。將先前的NSLog()標記改變一下:
- -(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
- {
- staticNSString*CellIdentifier=@"Cell";
- UITableViewCell*cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- cell.textLabel.text =[list objectAtIndex:indexPath.row];
- NSLog(@"the text is %@",[list objectAtIndex:indexPath.row]);
- return cell;
- }
現在你打印出來就是你的數據模塊的內容。運行這個app,看看顯示出來的是什么:
- Problems[7914:f803] the text is(null)
- Problems[7914:f803] the text is(null)
- Problems[7914:f803] the text is(null)
- Problems[7914:f803] the text is(null)
- Problems[7914:f803] the text is(null)
- Problems[7914:f803] the text is(null)
上面的很好的解釋了為什么在cell里面什么都沒有看到的原因:因為這個文字(text)始終是nil。然而,假如你檢查你的代碼,並且在initWithStyle:方法里面顯示的添加了很多的字符串到list array里面:
- [list addObject:@"One"];
- [list addObject:@"Two"];
- [list addObject:@"Three"];
- [list addObject:@"Four"];
- [list addObject:@"Five"];
就像上面那樣,這是測試你的假設是不是正確的一個很好的方法。可能你還想更准確的看看這個array里面到底有什么東西。改變先前在tableView:cellForRowAtIndexPath:里面的NSLog()為這樣:
- NSLog(@"array contents: %@", list);
至少這樣可以給你展示一些東西。運行這個app。假如你還沒准備好猜測會發生什么情況,調試窗口已經給你打印出來了:
- Problems[7942:f803] array contents:(null)
- Problems[7942:f803] array contents:(null)
- Problems[7942:f803] array contents:(null)
- Problems[7942:f803] array contents:(null)
- Problems[7942:f803] array contents:(null)
- Problems[7942:f803] array contents:(null)
哈哈,你的臉色瞬間陰沉下來。上面的代碼居然沒有起作用,因為你可能忘了在首先為這個array對象申請內存空間。這個“list”所以一直為nil,因此調用addObject: 和objectAtIndex:不會起任何的作用。
你應該在你的view controller被裝載的時候為這個list對象分配空間,因此在initWithStyle:方法里面應該是一個不錯的選擇。修改那個方法為:
- -(id)initWithStyle:(UITableViewStyle)style
- {
- if(self==[super initWithStyle:style])
- {
- list =[NSMutableArray arrayWithCapacity:10];
- [list addObject:@"One"];
- [list addObject:@"Two"];
- [list addObject:@"Three"];
- [list addObject:@"Four"];
- [list addObject:@"Five"];
- }
- returnself;
- }
試一試。我暈,依然什么都沒有!調試窗口輸出依然是:
- Problems[7971:f803] array contents:(null)
- ...and so on ...
經過了這么多假設和修改,但是還是什么都沒有,這些真的是非常令人沮喪啊,但是請記住你可能會一直繼續到最后,直到你弄清楚了所有的假設。所以現在的問題就是難道initWithStyle:沒有被調用?
Working With Breakpoints
你可能又會在代碼里面放置另外一個NSLog()標志,但是其實你完全可以使用另外的工具:斷點( breakpoints)。你已經看到過無論什么時候只要有異常拋出的時候,程序就會終止的異常斷點(Exception Breakpoint)了。你其實也可以增加其他的斷點,並且可以放置到代碼的任何地方。一旦你的程序運行到斷點的地方,這個斷點就會被觸發,並且程序就會進入調試模式。
你可以通過點擊代碼編輯區前面的行號來放置特殊的斷點:
這個藍色的箭頭所指示的那一行就有一個斷點了。你也可以在斷點導航器(Breakpoint Navigator)里面看到這個新的斷點:
再一次運行這個app。假如initWithStyle:確實是會被調用的話,那么你點擊了“Tap Me!”按鈕之后,當這個ListViewController被裝載的時候,這個app將會暫停,並且會進入調試器。
可能正如你所料的,什么事情也沒有發生。initWithStyle:沒有被調用。其實這個是可以講得通的,因為view controller是從storyboard(或者xib)中裝載的,所以使用的應該是initWithCoder:方法。
將之前initWithStyle:方法替換為initWithCoder::
- -(id)initWithCoder:(NSCoder*)aDecoder
- {
- if(self==[super initWithCoder:aDecoder])
- {
- list =[NSMutableArray arrayWithCapacity:10];
- [list addObject:@"One"];
- [list addObject:@"Two"];
- [list addObject:@"Three"];
- [list addObject:@"Four"];
- [list addObject:@"Five"];
- }
- returnself;
- }
並且保持斷點在這個方法上面,來看看它是怎么工作的:
一旦你點擊了那個按鈕,這個app將會進入調試器:
以上的情況並不是意味着這個app崩潰了!它只是在這個斷點處暫停了。在左邊的執行堆棧里面(假如你沒有看到執行堆棧的話,你可能需要切換到調試導航器),你可以看到你是從buttonTapped:到這里的。這個調試導航器里面,我們看到執行了一系列的UIKit的方法,並且裝載了一個新的view controller。(順便說句,斷點是一個非常好的工具來指出這個系統是怎么工作的。)
如果想要離開你之前停留的地方,繼續運行這個程序,簡單的就是點擊繼續程序運行按鈕,或者在調試控制台中輸入“c”。
顯然的是,一切並沒有如我們料想的一樣,這個app又奔潰了。我告訴過你,它有很多bug的。
注意:在你繼續之前,在initWithCoder:移除斷點或者使斷點無效。因為他已經展現了他的目的,所以現在它可以離開了。
你可以在顯示行號的的地方右擊斷點,並且在彈出的菜單中選擇刪除斷點。你也可以拖出這個斷點離開窗口,或者在斷點調試器里面移除。
假如你並不想移除這個斷點,你可以簡單的使斷點無效。為了達到這個目的,你可以使用右擊彈出菜單,或者左擊一次這個斷點。判斷這個斷點是否有效,你可以看看這個斷點的顏色,當為淺藍色了就是無效了,深藍色就是有效的。
Zombies!
回到這個崩潰。它是一個EXC_BAD_ACCESS,幸運的是調試器指到了他發生在那里,在tableView:cellForRowAtIndexPath:
這是一個EXC_BAD_ACCESS崩潰,意味着在你的內存管理里面有bug。不像SIGABRT,你將不會得到很明朗的錯誤消息。然而你可以使用一個讓你看到曙光的調試工具:Zombies!
打開這個項目的scheme editor:
選擇Run 選項,然后選擇Diagnosics標簽。勾上Enable Zombie Objects選項:
現在運行這個app。這個app仍然崩潰,但是現在你將會得到下面的錯誤消息:
- Problems[18702:f803]***-[__NSArrayM objectAtIndex:]: message sent to deallocated instance 0x6d84980
上面這個就是zombie enable 工具所做的,做個小概括:無論什么時候你創建了一個新對象(通過發送“alloc”消息),一塊內存將會為這個對象的實例變量保留。當這個對象被釋放,他的保留計數(retain count)變成0,這塊內存將會被釋放。
但是,你可能仍然有許多的指針指向這個已經失效的內存,這些都是建立在假設這里有一個有效的對象存在的情況下。假如你程序的某些部分試着使用這個野指針,這個app將會伴隨着EXC_BAD_ACCESS的錯誤崩潰掉。
(假如你是很幸運的話,這個程序將會崩潰。假如你沒那么幸運的哈,這個app將會使用這個死亡的對象,各種各樣的破壞可能相繼發生,特別是某個指針所指向的這個內存區域已經被一個新的對象重新分配了。)
當這個zombie工具被啟用之后,即使這個對象被釋放了,這個對象的內存也不會被清理。所以,那塊內存將會被標記為“長生不死的”。假如你試着之后又去使用這塊內存,這個app能夠意識到你的錯誤操作,並且app將會拋出“message sent to daellocated instance”錯誤並且終止運行。
因此這就是之前發生的事。這行就是使用了不死的對象:
- cell.textLabel.text =[list objectAtIndex:indexPath.row];
這個cell對象和他的textLabel應該是好的,那么indexPath也應該是正確的,因此我猜測在這個問題下,這個不死的對象應該是“list”。
你多半其實已經有個很好的線索來懷疑這個“list”,因為這個錯誤消息說:
- -[__NSArrayM objectAtIndex:]
這個不死的對象的類是__NSArrayM。假如你已經有一段時間的cocoa編程經驗,你應該就會知道一些基本的類,就像NSString和NSArray實際上是“class clusters”,這就意味着就像NSString或者NSArray這些原始的類在一些底層的地方會被特殊的類代替。所以在這里你可以看到一些NSArray類型的對象,也就是這個“list”其實應該是一個NSMutableArray。
假如你卻是想要確認一下,你可以增加一個NSLog()在分配了“list”數組那行代碼之后:
- NSLog(@"list is %p", list);
這里將會打印出和錯誤消息一樣的內存地址(在我這里的情況下是0x6d84980,但是你自己測試的時候,地址就會不一樣的)。
你也可以在調試器里面使用“p”的命令來打印出這個“list”變量的地址(和這個相對的命令就是“po”,這個命令將會打印出這個實際的對象,而不是地址)。這樣方便的地方就是你可以省略很多額外增加NSLog()的步驟和從新編譯這個app、
- (lldb) p list
注意:非常不幸的是,上面這些命令在xcode4.3里面並沒有執行的很好。由於一些原因,這個地址一直都是展示的0×00000001,可能是因為這個class cluster吧。
在GDB調試器下面,那些命令就執行的很好,在調試器的變量窗口展示出“list”都是zombie。因此我覺得這個是LLDB的bug。
為這個list 數組分配空間的地方就在initWithCoder:,就是下面這樣:
- list =[NSMutableArray arrayWithCapacity:10];
由於這里不是ARC(Automatic Reference Counting)(自動引用計數)項目,所以是人工管理內存,所以這里你需要retain這個變量:
- // in initWithCoder:
- list =[[NSMutableArray arrayWithCapacity:10] retain];
為了避免內存泄露,你也不得不在dealloc函數中釋放這個對象,就像下面這個:
- -(void)dealloc
- {
- [list release];
- [super dealloc];
- }
再一次運行這個app。它又崩潰在這同樣的一行,但是注意這個調試窗口輸出的東西改變了:
- Problems[8266:f803] array contents:(
- One,
- Two,
- Three,
- Four,
- Five
- )
由上面信息可以知道這個array已經分配了內存空間和包含了字符串的。這個崩潰的提示不再是EXC_BAD_ACCESS,而是SIGABRT,所以你需要再一次設置這個Exception Breakpoint。將這個解決了,繼續找其他的bug!
注意:即使你使用了ARC,在這樣的內存管理錯誤下也是一個非常大的事,你也會崩潰,得到一個EXC_BAD_ACCESS的錯誤,特別是假如你使用了不安全保留屬性。
我的小提議:無論你什么時候得到一個EXC_BAD_ACCESS錯誤,你都可以開啟zombie objects,然后再試試。
注意一點:你不應該一直啟用zombie objects。因為這個工具將永遠不會釋放內存,只是簡單標記一下這個內存是不死的,你最終將會在某個時候耗盡所有的內存。因此你應該在排查內存相關的錯誤的時候才開啟zombie objects,其他時候應該關閉它。
Stepping Through the App(單步調試)
使用斷點來解決這個新的問題。將斷點放置在剛剛崩潰那一行:
重新運行這個程序,點擊按鈕。你將會在第一次執行tableView:cellForRowAtIndexPath:的時候進入調試器。注意啊,這個時候,app只是因為斷點暫停了,並沒有崩潰。
你想要准確的知道這個程序崩潰時的一些細節。請點擊繼續執行按鈕,或者在(lldb)的提示后輸入“c”來繼續執行。程序將會從暫停的地方繼續執行。
什么事情也沒有發生,你仍然暫停在tableView:cellForRowAtIndexPath:這個函數的斷點處。但是在調試窗口卻顯示:
- Problems[12540:f803] array contents:(
- One,
- Two,
- Three,
- Four,
- Five
- )
這就意味着tableView:cellForRowAtIndexPath:在第一次執行的時候沒有任何問題,因為NSLog()在斷點之后執行了。因此這個app能夠很好地創建第一個cell。
假如你鍵入以下的到調試提示之后:
- (lldb) po indexPath
在調試窗口應該可以輸出下面的:
- (NSIndexPath*) $3 =0x06895680<NSIndexPath0x6895680>2 indexes [0,1]
以上重要的部分是[0, 1]。就是這個NSIndexPath對象為section 0和row 1。換句話說,這個tableview現在就在請求第二行。從這里我們可以推測這個app在第一次創建cell的時候沒有任何問題,正如剛剛這里就沒有發生崩潰。
多點幾次這個繼續按鈕。在某一個特定的時候,這個程序崩潰了,並且輸出一下錯誤消息:
- Problems[12540:f803]***Terminating app due to uncaught exception 'NSRangeException',
- reason:'*** -[__NSArrayM objectAtIndex:]: index 5 beyond bounds [0 .. 4]'
- ***Firstthrow call stack:
- ...and so on ...
假如你檢查這個indexpath對象的話,你可以看到:
- (lldb) po indexPath
- (NSIndexPath*) $11 =0x06a8a6c0<NSIndexPath0x6a8a6c0>2 indexes [0,5]
Section依然是0,但是這個row的索引是5。注意哦,這個錯誤的消息也是說“index 5”。因為計數是從0開始的,當到5的時候實際上意味着已經是6的位置了。但是這里只有5項。顯然這個tableview認為這里實際上有更多的行。
所以這個犯人就是下面的方法:
- -(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
- {
- return6;
- }
這個方法其實應該被寫成這樣的:
- -(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
- {
- return[list count];
- }
刪除斷點或者使斷點無效,然后再次運行這個程序。終於這個tableview顯示出來了,並且沒有了崩潰!
注意:這個“po”命令對於檢查你的對象是非常有用的。你可以在程序暫停在調試器的時候,或者在設置一個斷點的時候,或者在崩潰的時候,使用這個命令。你需要確定的是這個方法當前在調用堆棧里面是高亮的,否則這個調試器將找不到這個變量。
你也可以在調試窗口的左邊看到這些變量,但是就算看到了也不是很方便就能知道細節的:
Once more, with feeling
我剛剛說了沒有崩潰的現象了?好,現在我們來試試滑動刪除。這個app又終止了在tableView:commitEditingStyle:forRowAtIndexPath:
錯誤消息是:
- Problems[18835:f803]***Assertion failure in-[UITableView _endCellAnimationsWithContext:],
- /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046
這個錯誤看起來像是來自UIKit,並不是來自app的代碼。多次輸入幾次“c”來讓系統拋出異常,這樣可以你可以得到更多有用的信息:
- ***Terminating app due to uncaught exception 'NSInternalInconsistencyException',
- reason:'Invalid update: invalid number of rows in section 0. The number of rows
- contained in an existing section after the update (5) must be equal to the number
- of rows contained in that section before the update (5), plus or minus the number
- of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or
- minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
- ***Firstthrow call stack:...
經過這些,上面給你一個非常漂亮的解釋。這個app告訴這個tableview里面一行要刪除,但是某人卻忘記從數據源里面移除這行的數據。因此這個table view看起來沒有什么改變。修改這個這方法:
- -(void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath
- {
- if(editingStyle ==UITableViewCellEditingStyleDelete)
- {
- [list removeObjectAtIndex:indexPath.row];
- [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
- }
- }
太好了,看起來這樣做起效了,你終於有一個不會崩潰的app了。
Where to go from here?(何去何從)
記住下面幾點:
假如你的app崩潰了,第一件事就是找到是哪里崩潰了,為什么崩潰了。一旦你知道了這兩點,修復這個崩潰就很簡單了。調試器可以幫助你,但是你需要知道怎么樣讓他幫助你。
有些崩潰可能是隨機出現的,這個也是最困難的一個,特別是當你正在使用多線程。但是大多數,你可以試試,會發現一些固定的方法來讓你的程序每次崩潰。
你可以想出怎么使用最少的步驟來減少崩潰的現象,這樣你將找到一個好的方法來修復這個bug(也就是說他將不會發生)。但是假如你沒有確定不會再生了這個錯誤,你就絕不能確定你的修改已經修復了這個bug。
秘訣:
1.假如崩潰在main.m里面,就可以設置全局異常斷點(Exception Breakpoint)。
2.在異常斷點開啟的狀態下,你也沒有得到得到有用的信息。在這種情況下,多繼續幾次運行這個app,或者在調試提示后面輸入“po $eax”命令。
3.大多數崩潰的一般原因和一些bug都是在你的xib中或者storyboard中的連接丟失了或者是錯誤的連接。這些情況不會在編譯錯誤里面顯示,因此你一般不知道。
4.不要忽略編譯警告。假如你有編譯警告,就說明你有些東西可能會出錯。假如你不知道為什么你會到一個編譯警告,最好去搞明白它. 這些都是安全的做法!
5.在設備上調試可能會和在模擬器上面有些微的不同。這兩個環境不是完全一樣,你將會得到不同的結果。
例如,當你運行一個有問題的程序在iphone4上的時候,這第一個崩潰就會發生在NSArray初始化的時候,因為你缺少一個nil標記,而不是會因為當這個app執行setList:的時候的時候崩潰。所以說上面那個原則方法就可以幫你找到崩潰問題的根源本質。
不要忘記靜態分析工具(static analyzer tool),這個工具將會捕獲更多的錯誤。假如你是一個初學者,推薦你開啟它。你可以在Build Settings界面上為你的工程設置:
調試愉快吧!