Xcode 調試方法總結


編寫代碼過程中出現錯誤、異常是不可避免的。通常我們都需要進行大量的調試去尋找、解決問題。這時,熟練掌握調試技巧將很大程度上的提高工作效率。接下來就說說開發過程中Xcode的調試方法。

1. Enable NSZombie Objects (開啟僵屍對象)。 
這個技巧主要用來追終重復釋放的問題。個人認為,ARC推出以來。項目的基本是基於ARC環境。不用開發者主動去調用release去釋放對象,所以不用太在意這個方法。這里就不多做介紹了。想了解該方法的同學請 坐飛機 
2. 斷點調試(全局斷點、條件斷點) 
一、全局斷點:

NSArray *aa = @[@2,@4]; NSLog(@"%@",aa[3]);
  • 1
  • 2

這兩行代碼,沒有添加全局斷點時,運行crash,直接就跳到了mian函數,如下圖:

這里寫圖片描述

接下來添加全局斷點,方法如下圖:

添加全局斷點的方法

添加之后運行,奔潰后,程序停留在了crash那行代碼。

這里寫圖片描述
是不是很方便,很省事。哈哈!(ps 不過有的crash,這種方式定位不到)

二、條件斷點:設置斷點觸發的條件,方便開發者對特定情況進行調試 
如下圖: 
在for循環中添加一個斷點。右擊斷點選擇”Edit BreakPoint”,然后設置斷點觸發條件。 
這里寫圖片描述

這個例子當 “i==5”時,斷點觸發,如下圖: 
這里寫圖片描述

3. Static Analyzer (靜態分析) 
Static Analyzer主要用於分析內存,避免內存泄漏。主要對以下情況進行分析。 
未使用的實例變量、未初始化的實例變量、類型不兼容、無法達到的路徑、引用空指針 
使用:command + shift +B,如下圖就能輕松找到可能內存泄漏的代碼,然后我們根據代碼環境進行修復就可以了(ps:有的內存泄漏可能檢測不出來,還是需要我們在寫代碼時對內存這塊多留點心。)

這里寫圖片描述

4. LLDB調試器 
這個方法是我今天主推的方法。比較高級,也更加靈活、方便。 
隨着Xcode5,LLDB調試器已經取代了GDB,成為了Xcode工程中默認的調試器。其實Xcode已經幫我們完成了大部分工作,而且很多東西也可以在Xcode中直接看到。所以這里我們只列舉常用的命令。 
打印:p,print的縮寫:該命令如果打印的是簡單類型則會列出簡單類型的的類型和值,如果是對象會打印出對象的地址。 
po,print Object 的縮寫,用於輸出OC對象 
如下如,當運行到斷點處時,控制台就會出現LLDB的調試命令行。我們只需在這里進行調試。 
這里寫圖片描述

expr:expression的縮寫,可以在調試時動態執行指定表達式,並將結果打印出來。常用於在調試過程中修改變量的值。 
如上圖,你在控制台輸入  
expr a=2 
你就能看到 
(NSInteger) $11 = 2 
這是a的值就被動態改成了2 
除此之外,還可以使用這個命令生成一個新的對象,如: 
expr int $b = 0 
p $b 這條命令用於輸出新申明對象的值(注意要加$)

image: image命令可用於尋址,有多個組合命令,在控制台輸入help image可查看image的用法。比較實用的用法是用於尋找棧地址對應的代碼位置,下面我們來舉個例子:

NSArray *array = @[@1,@2]; NSLog(@"%@",array[2]);
  • 1
  • 2

這段代碼很明顯會crash,運行之后拋出下面的異常

2016-03-23 22:26:11.014 Test[3631:136626] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]' *** First throw call stack: ( 0 CoreFoundation 0x0000000104f28f45 __exceptionPreprocess + 165 1 libobjc.A.dylib 0x00000001049a2deb objc_exception_throw + 48 2 CoreFoundation 0x0000000104e17b14 -[__NSArrayI objectAtIndex:] + 164 3 Test 0x00000001044a5829 -[ViewController viewDidLoad] + 265 4 UIKit 0x0000000105467cc4 -[UIViewController loadViewIfRequired] + 1198 5 UIKit 0x0000000105468013 -[UIViewController view] + 27 6 UIKit 0x000000010534151c -[UIWindow addRootViewControllerViewIfPossible] + 61 7 UIKit 0x0000000105341c05 -[UIWindow _setHidden:forced:] + 282 8 UIKit 0x00000001053534a5 -[UIWindow makeKeyAndVisible] + 42 9 UIKit 0x00000001052cd396 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131 10 UIKit 0x00000001052d39c3 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1750 11 UIKit 0x00000001052d0ba3 -[UIApplication workspaceDidEndTransaction:] + 188 12 FrontBoardServices 0x0000000107c83784 -[FBSSerialQueue _performNext] + 192 13 FrontBoardServices 0x0000000107c83af2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45 14 CoreFoundation 0x0000000104e55011 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 15 CoreFoundation 0x0000000104e4af3c __CFRunLoopDoSources0 + 556 16 CoreFoundation 0x0000000104e4a3f3 __CFRunLoopRun + 867 17 CoreFoundation 0x0000000104e49e08 CFRunLoopRunSpecific + 488 18 UIKit 0x00000001052d04f5 -[UIApplication _run] + 402 19 UIKit 0x00000001052d530d UIApplicationMain + 171 20 Test 0x00000001044a5baf main + 111 21 libdyld.dylib 0x000000010764c92d start + 1 22 ??? 0x0000000000000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

現在我懷疑出錯的地址是0x00000001044a5829(可根據執行文件名或最小的棧地址判斷)為進一步精確定位我們可輸入以下命令image lookup --address 0x00000001044a5829 
命令執行后返回結果如下:

Address: Test[0x0000000100001829] (Test.__TEXT.__text + 265) Summary: Test`-[ViewController viewDidLoad] + 265 at ViewController.m:21
  • 1
  • 2

由此,我們可以看出出錯的地方是ViewController.m文件的第21行。 
我們還可以使用image lookup命令查看具體的類,如下:

(lldb) image lookup --type UIView
Best match found in /Users/jamalping/Library/Developer/Xcode/DerivedData/Test-gviuudbzlyhssmanjxpwhchdbscz/Build/Products/Debug-iphonesimulator/Test.app/Test:
id = {0x00001e8d}, name = "UIView", byte-size = 8, decl = UIView.h:144, clang_type = "@interface UIView : UIResponder @property ( getter = isUserInteractionEnabled,setter = setUserInteractionEnabled:,assign,readwrite,nonatomic ) BOOL userInteractionEnabled; @property ( getter = tag,setter = setTag:,assign,readwrite,nonatomic ) NSInteger tag; @property ( readonly,getter = layer,setter = <null selector>,nonatomic ) CALayer * layer; @property ( readonly,getter = isFocused,setter = <null selector>,nonatomic ) BOOL focused; @property ( getter = semanticContentAttribute,setter = setSemanticContentAttribute:,assign,readwrite,nonatomic ) UISemanticContentAttribute semanticContentAttribute; @end "
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

call 
call:即調用,如我們在viewDidLoad: 設置一個斷點,在程序中斷的時候輸入call self.view.backgroudColor = [UIColo redColor]繼續運行程序,view就變成紅色了,在調試的時候靈活運用call命令可以達到事半功倍的效果。

 

 

最近一直沒有更新簡書是因為在開發和測試階段,有任務,沒有進行學習,不過在做任務的時候也遇到了一些技術點,在這里總結一下。

      今天我們來談一談�Xcode調試的技巧。�就像玩游戲,有些玩家他們知道怎么操作,會放技能會走路,但是他們不知道買裝備,玩了一局下來,鞋子小刀都沒有買,這樣行走江湖很危險啊!所以我們出門要把裝配佩戴好,學會裝備自己才是王道!

      本文將簡書和各大博客上邊涉及該方面的內容進行匯總,並試圖進行全方位的總結,如果有什么不足之處還希望大家給提出來,我會進行補充和修改。現在我們開始吧。

       總體看來,關於調試的方法包括以下幾個:�日志輸出&LLDB、斷點、性能、一些小技巧等幾個大的方面。我們一一進行學習和總結。

       嘗試接受新鮮事物和方法,方法都是熟能生巧的,各種方法綜合運用,用好了會事半功倍。

1、�日志輸出&LLDB

       關於日志輸出,我們最先想到的是NSLog,但是弊端在於我們需要在想要打印的位置添加NSLog代碼並重新運行項目,這樣耽誤時間,所以我們平時用的比較多的是打斷點,然后po一下。這個“po”就是LLDB里面的一句命令。

       那么,什么是LLDB呢:它是一個有着 REPL 的特性和 C++ ,Python 插件的開源調試器。LLDB 綁定在 Xcode 內部,存在於主窗口底部的控制台中。調試器允許你在程序運行的特定時暫停它,你可以查看變量的值,執行自定的指令,並且按照你所認為合適的步驟來操作程序的進展。我們可以簡單的理解成它是一個調試器

    (1)LLDB命令行

       像下圖中,我們打斷點后,控制台右邊里面會出現一個“lldb”,我們平時不怎么關注它,但我們一直在使用它。

 
 

      <1>  help命令

      斷點的時候,我們在控制台右邊lldb后邊輸入一個“help”,然后敲回車,就會看到所有關於lldb的命令以及各自的介紹,如下圖:

 
 
 
 

      <2>  print命令

       print很好理解,就是打印,使用過程中我們可以直接用p來代替print。

       <3>  expression命令

        該命令可以改變程序實際參數的值,目的是方便了調試:不用重新運行項目。例如下圖中,我們簡單的做一個測試,令蘋果=1,橘子=2,all應該=3,在斷點過程中,我們用expression命令修改了橘子的值,令橘子=5,結果再打印all的時候,all=6(親測好使)。使用過程中我們可以直接用 expr來代替expression。

 
 

        這里我們注意到一個“$9”,這里的9是我們使用lldb命令的次數,例如下圖,我們expression一次橘子,po了一次all,再print橘子的時候,顯示的是“$11”,說明我們print命令是$10。這個不用管,只是提醒我們一下而已,作用應該不是很大。

 
 

       <4>  po命令

        現在我們來看看平時用的比較多的“po”,它是“print object”的簡寫。po一下,我們就可以看到對象的詳細信息。po使用的比較多,使用起來也比較簡單、方便,這里不做多余的介紹。

        <5>  image命令

image list 查看工程中使用的庫

image lookup --address  0x000000010e0979ac  程序崩潰的時候定位,查看具體報錯位置

       這個其實我們可以想辦法用在我們崩潰日志的收集里面,這樣的話我們就可以直接定位到崩潰信息的具體位置了。(親測,不好使。感覺xcode反饋的崩潰信息不准確。)

       <6>  call命令

        call即調用的意思。上述的po和p也有調用的功能。所以一般只在不需要顯示輸出,或方法無返回值的時候使用call。 和上面的命令一樣,我們依然在viewDidLoad:里面設置斷點,然后在程序中斷的時候輸入下面的命令:

call  [self.view setBackgroundColor:[UIColor redColor]]

        繼續運行程序,看看view的背景顏色是不是變成紅色的了!在調試的時候靈活運用call命令可以起到事半功倍的作用(親測好使)。

(2)LLDB調試欄

 
 

      一般的按鈕和功能我們用的比較多,也比較熟悉,這里我們着重介紹一下Debug View Hierachy和xcode8新增的memory graph功能。

      <1>Debug View Hierachy

 
 

       Debug View Hierachy,翻譯過來就是:調試視圖層次。除了點擊控制台出的圖標,也可以從菜單中選擇Debug > View Debugging > Capture View Hierarchy 來啟動視圖調試。(我們可以看出xcode開發人員的用心之處:重疊在一起的長方形,我們大概就明白這個按鈕是表示層級關系的)在斷點或者不是斷點的情況下都可以通過點擊這個按鈕查看視圖層級關系。點擊按鈕,我們會在xcode最頂端的地方看到下圖的一個信息:

 
 

       capture user interface for YourAppName:capture是捕獲的意思,interface,face我們知道是臉,inter是進入的意思,interface就是進入臉,我們大概能夠明白這句話的意思就是“為你的app捕獲用戶交界面”。

 
 

從左到右控件排序:(上圖中也簡單解釋了各個功能)

調整視圖間距:調整不同視圖間的間距。

展示被剪切的內容:當前展示視圖中被剪切的部分。

展示約束:展示選中視圖的約束。

重置查看區域:將3D渲染透視圖恢復至默認狀態。

調整查看模式:選擇性地展示3D渲染透視圖,比如僅展示內容,僅展示框架以及同時展示內容和框架。

縮小:縮小3D渲染透視圖

恢復:將3D渲染透視圖恢復至默認尺寸。

放大:放大3D渲染透視圖

調整可視視圖范圍:隱藏視圖或展示視圖,一步步解析3D渲染視圖,向左或者向右滑動滑塊兒有相反的效果。

       有了這個圖層關系,我們可以很清楚的知道頁面上邊的各個控件的位置關系,因為我們在開發階段�尤其是測試階段,某個控件上邊的字不顯示,或者控件的字被遮擋,我們可以用視圖調試器查看,是否控件是frame設置的不合理。

        <2>memory graph

        【經驗1】

        這個是xcode8新增的功能,翻譯過來的意思就是:內存圖。有了內存圖我們就可以解決閉包引用循環問題了。舉個栗子,我們寫個循環引用,如下:

 
 
 
 
 
 

     (說實在的,這幾個命令我在終端不知道怎么調用,試了半天,還是沒有搞出來,應該就是內存圖調試的樹狀結構。如果有誰在終端里面知道怎么搞出來,煩請告訴我一下具體怎么操作,謝謝了!)所以,這里我們直接看這個memory graph按鈕點擊后的效果。

      【經驗2】   

       說明1:這個功能是xcode8新增的功能,那么xcode7上邊肯定找不到!而xcode8還要10.11.5以上的系統,所以,建議大家先把升級電腦系統,然后安裝xcode8。

       說明2:  真機的話還需要iOS9或者10的系統。

       說明3:查找當前默認Xcode.app的developer路徑---終端命令行:xcode-select -p。

                    如果安裝了多個版本的xcode工具,可以使用xcode-select命令指定命令行指令使用哪個版本xcode下的developer目錄下的調試工具,即修改路徑:xcode-select --switch /Applications/Xcode2.app/Contents/Developer。

      踩過的坑:

      <1>本來我用的是xcode7.2,挺好用,結果在xcode7上邊顯然沒有這個按鈕,升級到xcode8吧。

      <2>同事airdrop傳來的xcode8.0和xcode8.1,結果提示安裝不上,需要升級電腦的系統。

      <3>升級好了系統,安裝好了同事airdrop傳來的xcode8.0不顯示。(!准備開始抓狂  !)難道需要8.1才行?!

      <4>安裝好了同事airdrop傳來的xcode8.1,依舊不顯示。(!!!抓狂!!!)難道是�打開xcode時候的路徑不對?!

      <5>修改打開xcode的路徑為xcode8.1,依舊不顯示。(早已經料到是這個結果了)難道是xcode安裝的太多了?!

      <6>卸載掉xcode7.0.1、7.2、7.3、8.0,只剩下一個8.1,依舊不顯示。(淡定的接受這個結果)難道是同事傳的xcode包有問題?!在App Store上自己下載!!

        <7>�下載好了,安裝好了,依舊不顯示。(�生無可戀了。。。)

        就在此時,我不知道是被逼瘋了還是靈光一閃,拿了同事的真機運行項目,結果居然有了memory graph按鈕!!!!

當你認為最困難的時候,恰恰也就是你最接近成功的時候!

當你放棄的時候,你永遠不會知道你離成功是那么的接近!

成功很簡單,就是在你堅持不住的時候,再堅持一下!

所以建議使用memory graph功能之前確保:

升級電腦系統並安裝xcode8.1。(8.1比8.0更穩定)(這里其實並不非得是App Store自己下載,別人用airdrop傳的也行)

真機iOS系統在9.0以上。

      【經驗3】

       當你點擊這個按鈕以后,xcode上邊的狀態條會顯示下圖:

 
 

       Capturing memory graph for YourAppName:翻譯過來就是正在捕捉你的app內存圖。

       來來來,我們先來看看這狗東西......不是,是點擊后的效果圖😂😂😂

 
 

      我想說,這是啥玩意?!

 
 

    【經驗4】

     在解釋上邊圖結構和各個圖標表示的含義之前我們先來看看這幾個嘆號分別代表什么。

 
 

紅色嘆號:這個最常見,Error=錯誤

黃色嘆號:這個也常見,Warning=警告

紫色嘆號:這個不常見,不過使用了memory graph我們就會經常見到,Runtime Issue=運行時問題

藍色箭頭:這個是檢查內存泄露是見到的,靜態分析Command+Shift+B就可以看見。Static Analyzer Issue=靜態分析的問題

紅色叉叉:這個實在instruments里面Leaked用法的時候見到過。UI Test Failed=UI測試失敗

在memory graph狀態下我們點擊左邊的嘆號,就會看到RunTime Issue:

 
 

      【經驗5】

       在解決這個紫色嘆號之前我們先來看看右邊灰色三棱錐、藍色正方體、綠色圓圈等等都代表的是什么。

綠色的一般都是 UIKit 控件及其子類

藍色一般 NSObject 類及其子類

黃色一般都是容器類型及其子類

灰色括號是指 block

      當然還有很多一些其他的類型,具體的大家去看右上角的 Memory Inspect 界面就好,上面都會有詳細的信息。

    【經驗6】

      當我們在某一個塊上邊點擊右鍵的時候,會彈出一個選項框,里面有5個選項:

 
 
 
 

Quick Look:快速查看,和上圖中的小眼睛功能一樣。

Print Description:打印詳細信息,和上圖小眼睛右邊的按鈕功能一樣。

Jump To Definition:跳至代碼區。

Reveal in Debug Navigator:在左邊的內存樹狀結構中標藍色。

Focus on Node:在節點上關注,點擊后只剩下跟自己前后箭頭相關的節點node。

        一開始進入首頁的時候,只展示tabbar上邊的四個controller。而當我們想要看某個頁面的memory graph的時候,我們需要在真機上邊跳到那個頁面,然后再點擊memory graph按鈕,才能在左邊的樹狀結構中找到想要看的頁面的controller。

      【經驗7】

        和紅色、黃色嘆號一樣,紫色嘆號出來了,我們就要想辦法解決掉,那么我們看看項目中的紫色嘆號都標記在哪里了呢?

 
 

       各種Reachability啊!!!好不容易找到這個memory graph按鈕,好不容易看明白點了內存圖,好不容易找到了紫色嘆號,你踏瑪德告訴我這個錯誤是因為蘋果自己的Reachability造成的!!!(生無可戀。。。。。。。。。。。。。。。。。)紫着吧。

2、斷點

斷點里面根據作用和功能也有很多種類:普通斷點、條件斷點、異常斷點、符號斷點等。我們一一學習介紹。

(1)普通斷點(不帶技能就出去闖盪江湖的)

        當程序運行到斷點處時會暫停運行。比如斷點打在11行,那么程序就會停在11行(注意:程序只運行到了前10行,第11行其實還沒有被執行。)。只要在代碼行旁邊點擊,就能添加一個斷點,再次點擊,斷點變成淺藍色,就能讓斷點不可用(disable了,仍然存在,只是不起作用了)。

 
 

(2)條件斷點(帶上技能再闖盪江湖)

打上斷點之后,對斷點進行編輯,設置相應過濾條件。單擊右鍵會彈出選項框:

 
 

Edit BreakPoint... :編輯斷點。

Disable  BreakPoint :斷點失效。(相當於上邊說到的單擊斷點變成淺藍色,斷點失效)

Delete BreakPoint :刪除斷點。

Reveal in BreakPoint Navigator :在左邊的斷點樹狀結構表明該斷點。

這里我們主要用到的是第一個:Edit BreakPoint。這里面設置斷點的篩選條件(雙擊斷點也可以快速進入編輯斷點的對話框)。

 
 

【1】Condition:返回一個布爾值,當布爾值為真觸發斷點,一般里面我們可以寫一個表達式。

【2】Ignore:      忽略前N次斷點,到N+1次再觸發斷點。

【3】Action:      斷點觸發事件,分為六種:

           <1>  AppleScript:執行腳本。

           <2>  Capture GPU Frame:用於OpenGL ES調試,捕獲斷點處GPU當前繪制幀。 

           <3>  Debugger Command:和控制台中輸入LLDB調試命令一致。

           <4>  Log Message:輸出自定義格式信息至控制台。

           <5>  Shell Command:接收命令文件及相應參數列表,Shell Command是異步執行的,只有勾選“Wait until done”才會等待Shell命令執行完在執行調試。

            <6>  Sound:斷點觸發時播放聲音。

【4】Options(Automatically continue after evaluating actions選項):選中后,表示斷點不會終止程序的運行。

【1】Condition:這里我設置i==6,我們看LLDB控制台打印結果:

 
 

這里打印了0-5,然后斷點斷了。這樣做的目的就是我們不用在循環里面一個一個的點擊下一步,直接跳至我們想要看到的那一步。

【2】Ignore:這里我把Condition的條件取消,設置Ignor的條件為3,我們看LLDB控制台打印結果:

 
 

結果是將0-2的循環直接忽略,而后邊的循環依舊每次在斷點的位置斷一次。

(3)  異常斷點 Exception Breakpoint(全局斷點)

      異常斷點可以快速定位不滿足特定條件的異常,比如常見的數組越界,這時候很難通過異常信息定位到錯誤所在位置。這個時候異常斷點就可以發揮作用了。

     添加異常斷點:

 
 

        同樣的,全局斷點也是可以編輯的,單擊右鍵或者雙擊斷點就會彈出編輯框,編輯的項目和上述是一樣的。

(4)符號斷點 Symbolic Breakpoint

        符號斷點的創建也同異常斷點。一般符號斷點可以在你指定的[類名 方法名]時中斷執行

 
 

3、性能檢測:

 

(1)靜態分析:通過對代碼靜態分析,找出代碼潛在的錯誤,如內存泄漏、空引用、未使用函數等。

                方法:菜單“Product"->"Analyze"或者Shift+Command+B,然后想辦法消滅藍箭頭

(2)動態分析:通過Instruments工具跟蹤分析程序運行時的數據

                方法:參考《Instruments性能檢測

4、其他小技巧

       (1)模擬器調試:FPS

        在《Instruments性能檢測》一文中我們就介紹了FPS=Frame Per Sencond:�一秒鍾渲染多少幀

        根據蘋果全球開發者大會WWDC的說法,當FPS低於45時,用戶就會察覺滑動有卡頓。

        編譯並運行應用程序,選中模擬器,從 Debug菜單中選擇Color Blended Layers選項。

 
 
 
 

紅色:表示這些layer是透明的,系統在渲染這些像素點的時候,需要將該view及view一下的其他view混合之后才能得到實際的顏色。紅色越深,表明系統在渲染的時候越費勁。

綠色:表示這些layer是不透明的,易於渲染。

黃色:表示這里的點無法直接繪制在屏幕上,此時系統需要對相鄰的像素點做反鋸齒計算,增加了圖形負擔。產生的原因是這個控件的背景是通過圖片拉伸得到的。

所以推薦盡可能地使用不透明的圖層。

      (2) 真機調試:截圖。

       當我們在模擬器上邊運行項目的時候,想要給產品或者測試人員看一下頁面效果如何,qq截圖就可以了,如果在真機上呢怎么截圖呢?一般我們會拿着真機給產品或者測試人員看看,但是如果來回折騰很麻煩,我們也可以用自己的手機照相然后發圖片給他們,這里還有一個更好的辦法對真機進行截圖:運行項目,選擇Debug--View Debugging--Take Screenshot of 真機名字,這樣在你的電腦桌面上邊就會有一張你的真機上邊選好頁面的截圖。

 
 

 

 

 

 

參考:1、iOS各種調試技巧豪華套餐

           2、iOS調試之LLDB

           3、Xcode8調試黑科技:Memory Graph實戰解決閉包循環引用問題

           4、iOS開發之Xcode常用調試技巧總結

           5、Xcode8的Debug新特性---WWDC 2016 Session 410 & 412 學習筆記

           6、iOS 開發之個人調試技巧

 



一、Xcode調試技巧之:NSLog

上面也提到了,在我們日常的開發過程中最常見的Debug方式就是打Log。而在OC語言中,打Log是采用NSLog方法。但是NSLog效率低下,具體原因可以看這篇博客(《NSLog效率低下的原因及嘗試lldb斷點打印Log》)。所以在平時的開發過程中,能不打Log就不打Log。實在想打Log網上也有對NSLog的一些優化方法,可以閱讀王巍的《宏定義的黑魔法 - 宏菜鳥起飛手冊》如下代碼便出自其中:

1
2
3
4
5
6
7
#define NSLog(format, ...) do {                                             \
fprintf(stderr,  " %s\n" ,                                           \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],  \
__LINE__, __func__);                                                        \
(NSLog)((format),  ##__VA_ARGS__);                                           \
fprintf(stderr,  "-------\n" );                                               \
while  (0)

另外在使用NSLog的時候應當注意,release版本中應該要去掉NSLog。

二、Xcode調試技巧之:LLDB

1、po:print object的縮寫,表示顯示對象的文本描述,如果對象不存在則打印nil。

簡單的打印一個對象我們就不說了,我們來說說特殊的應用場景吧!

應用場景:你想知道一個視圖包含了哪些子視圖。當然你可以循環打印子視圖,但是下面只需要一個命令即可解決。

輸出視圖層級關系(這是一個被隱藏的命令):po [[self view] recursiveDescription]

還有個常見的調試場景,比如你要打印一個model。你直接用NSLog或po對象處理的結果是model的地址,這並不是我們想要的。怎么辦?有沒有解決方法呢?

答案是有的。你可以重寫model里面的description方法。但是,如果model里屬性非常多,這樣就不適用了。你不可能說在description方法里面拼接屬性返回。這樣不僅麻煩,而且可讀性非常差。到這里,我們可以利用runtime動態獲取屬性並返回。不過我並不建議你重寫description方法,我推薦你重寫debugDescription方法(至於詳細的介紹以及如何重寫請點擊此處),因為debugDescription方法和description方法效果一樣,區別在於debugDescription方法是在你使用po命令時調用的,實際上也是調用了description方法。

2、p:可以用來打印基本數據類型。

3、call:執行一段代碼

1
call NSLog(@ "%@" , @ "yang" )

4、expr:動態執行指定表達式

expr i = 101

輸出:(int)$0 = 101

5、bt:打印當前線程堆棧信息

如果要打印所以線程堆棧信息,使用:bt all即可。

6、image:常用來尋找棧地址對應代碼位置:

舉個栗子:

應用場景(數組越界)模擬代碼:

1
2
NSArray *array = @[@ "yang" ,@ "she" ,@ "bing" ];
NSLog(@ "%@" ,array[3]);

錯誤信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
*** Terminating app due to uncaught exception  'NSRangeException' , reason:  '*** -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 2]'
*** First  throw  call stack:
(
0   CoreFoundation                      0x000000010579734b __exceptionPreprocess + 171
1   libobjc.A.dylib                     0x00000001051f821e objc_exception_throw + 48
2   CoreFoundation                      0x00000001056d1eeb -[__NSArrayI objectAtIndex:] + 155
3   BGMultimediaDemo                    0x0000000104c25550 -[ViewController viewDidLoad] + 192
4   UIKit                               0x0000000105d5c06d -[UIViewController loadViewIfRequired] + 1258
......
......
......
21  BGMultimediaDemo                    0x0000000104c25adf main + 111
22  libdyld.dylib                       0x000000010857268d start + 1
23  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating  with  uncaught exception of type NSException

這個時候我們如果懷疑出錯的地址是0x0000000104c25550,那么我們可以使用下面命令來找出錯誤代碼的位置:

image lookup --address 0x0000000104c25550

執行命令后輸出結果如下:

Address: BGMultimediaDemo[0x0000000100001550] (BGMultimediaDemo.__TEXT.__text + 192)

Summary: BGMultimediaDemo`-[ViewController viewDidLoad] + 192 at ViewController.m:30

從上面輸出結果中可以看出,錯誤位置應該是ViewController.m文件中的30行。是不是超級好用?反正我覺得好用。

三、Xcode調試技巧之:斷點(Breakpoint)

斷點,程序員Debug必備技之一。

1、條件斷點

打上斷點之后,對斷點進行編輯,設置相應過濾條件。下面簡單的介紹一下條件設置:

Condition:返回一個布爾值,當布爾值為真觸發斷點,一般里面我們可以寫一個表達式。

Ignore:忽略前N次斷點,到N+1次再觸發斷點。

Action:斷點觸發事件,分為六種:

  • AppleScript:執行腳本。

  • Capture GPU Frame:用於OpenGL ES調試,捕獲斷點處GPU當前繪制幀。

  • Debugger Command:和控制台中輸入LLDB調試命令一致。

  • Log Message:輸出自定義格式信息至控制台。

  • Shell Command:接收命令文件及相應參數列表,Shell Command是異步執行的,只有勾選“Wait until done”才會等待Shell命令執行完在執行調試。

  • Sound:斷點觸發時播放聲音。

這些功能平時在調試程序的過程中都可以進行嘗試,說實話我用的設置Condition項會較多些。

Options(Automatically continue after evaluating actions選項):選中后,表示斷點不會終止程序的運行。

38.jpg

2、異常斷點

異常斷點可以快速定位不滿足特定條件的異常,比如常見的數組越界,這時候很難通過異常信息定位到錯誤所在位置。這個時候異常斷點就可以發揮作用了。

添加異常斷點:

39.jpg

編輯異常斷點:

40.jpg

Exception:可以選擇拋出異常對象類型:OC或C++。

Break:選擇斷點接收的拋出異常來源是Throw還是Catch語句。

3、符號斷點

符號斷點的創建方式和異常斷點一樣一樣的,在符號斷點中可以指定要中斷執行的方法:

舉個例子,常見的場景,我想讓它執行到ViewController類中的viewWillAppear方法就中斷執行:

42.png

Symbol:[ViewController viewWillAppear:]即[類名 方法名]可以執行到指定類的指定方法中開始斷點。如果只有viewWillAppear:即方法名,它會執行到所以類中的viewWillAppear:方法中開始斷點。

四、Xcode調試技巧之:EXC_BAD_ACCESS

1、開啟僵屍對象

開啟Zombie模式之后會導致內存上升,因為所以已經被釋放(引用計數為0)的對象被僵屍對象取代,並未真的釋放掉。這個時候再給僵屍對象發送消息,就會拋出異常,並打印出異常信息,你可以輕松的找到錯誤代碼位置,結束Zombies時會釋放。它的主要功能是檢測野指針調用。

使用方法:

“Edit Scheme…” —> “Run” —> “Diagnostics” —> “Zombie Objects”

打開”Edit Scheme…”窗口:

43.png

開啟Zombie模式:

44.png

注意:Zombie模式不能再真機上使用,只能在模擬器上使用。

2、Address Sanitizer(地址消毒劑)

在Xcode7之后新增了AddressSanitizer工具,為我們調試EXC_BAD_ACCESS錯誤提供了便利。當程序創建變量分配一段內存時,將此內存后面的一段內存也凍結住,標識為中毒內存。程序訪問到中毒內存時(訪問越界),立即中斷程序,拋出異常並打印異常信息。你可以根據中斷位置及輸出的Log信息來解決錯誤。當然,如果變量已經釋放了,它所占用的內存也會被標識為中毒內存,這個時候訪問這片內存空間同樣會拋出異常。

使用方法:

“Edit Scheme…” —> “Run” —> “Diagnostics” —> “Zombie Objects”

45.png

開啟AddressSanitizer之后,在調試程序的過程中,如果有遇到EXC_BAD_ACCESS錯誤,程序則會自動終端,拋出異常。

五、結語

文中提到的這些只是iOS開發過程中比較常見的一部分Debug方式。其他的還有比如說:Profile,Analyze分析,View Hierarchy(在調試視圖顯示異常時用的比較多)等,有興趣的可以自行了解。

本文內容中部分來自網絡,后續會不斷更新完善。歡迎一起學習交流!

 

六、參考博客地址:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM