有6種方法,分別是:html
從NSLog打印出的任何東西都會變成代碼,任何人均可以看見。將設備鏈接到電腦,打開XCode中的organizer,就能夠從console查看到每條日誌信息,這會帶來很大的影響。想一下,你想把一些保密的算法邏輯或者用戶密碼打印到console。正由於這個,若是蘋果發如今項目發佈中,有太多內容輸出到console,那麼你的應用可能會遭到蘋果的拒絕。python
NSZombieEnabled變量用來調試與內存有關的問題,跟蹤對象的釋放過程。啓用了NSZombieEnabled的話,它會用一個殭屍來替換默認的dealloc實現,也就是在引用計數降到0時,該殭屍實現會將該對象轉換成殭屍對象。殭屍對象的做用是在你向它發送消息時,它會顯示一段日誌並自動跳入調試器。ios
因此,當在應用中啓用NSZombie而不是讓應用直接崩潰掉時,一個錯誤的內存訪問就會變成一條沒法識別的消息發送給殭屍對象。殭屍對象會顯示接受到得信息,而後跳入調試器,這樣你就能夠查看究竟是哪裏出了問題。git
能夠在Xcode的scheme頁面中設置NSZombieEnabled環境變量。點擊Product——>Edit Scheme打開該頁面,而後勾選Enable Zombie Objects 複選框。github
而後選中下圖中的選項算法
Zombie模式不能再真機上使用,只能在模擬器上使用express
1、全局斷點(異常斷點):異常斷點能夠快速定位不知足特定條件的異常,好比常見的數組越界,這時候很難經過異常信息定位到錯誤所在位置。這個時候異常斷點就能夠發揮做用了。小程序
NSArray *aa = @[@2,@4]; NSLog(@"%@",aa[3]);
這兩行代碼,沒有添加全局斷點時,運行crash,直接就跳到了mian函數,以下圖:數組
接下來添加全局斷點,方法以下圖:框架
點擊了Exception BreakPoint 後
添加以後運行,奔潰後,程序停留在了奔潰的那行代碼。
Exception:能夠選擇拋出異常對象類型:OC或C++。
Break:選擇斷點接收的拋出異常來源是Throw仍是Catch語句。
ps 不過有時候程序奔潰,這種方式定位不到
2、條件斷點:設置斷點觸發的條件,方便開發者對特定狀況進行調試
添加(刪除)條件斷點的快捷鍵:command+\
以下圖:
在for循環中添加一個斷點。右擊斷點選擇」Edit BreakPoint」,而後設置斷點觸發條件。
這個例子當 「i==5」時,斷點觸發,以下圖:
3、符號斷點:
選擇Symbolic Breakpoint後
例如:unrecognized selector send to instancd 的快速定位
NO4:
設置完成後再遇到相似的錯誤就會定位到具體的代碼
Static Analyzer主要用於分析內存,避免內存泄漏。主要對如下狀況進行分析。
未使用的實例變量、未初始化的實例變量、類型不兼容、沒法達到的路徑、引用空指針
使用快捷鍵:command + shift +B,能夠去以下圖中去設置
以下圖就能輕鬆找到可能內存泄漏的代碼,而後咱們根據代碼環境進行修復就能夠了
(ps:有的內存泄漏可能檢測不出來,仍是須要咱們在寫代碼時對內存這塊多留點心。)
在Xcode7以後新增了AddressSanitizer工具,爲咱們調試EXC_BAD_ACCESS錯誤提供了便利。當程序建立變量分配一段內存時,將此內存後面的一段內存也凍結住,標識爲中毒內存。程序訪問到中毒內存時(訪問越界),當即中斷程序,拋出異常並打印異常信息。你能夠根據中斷位置及輸出的Log信息來解決錯誤。固然,若是變量已經釋放了,它所佔用的內存也會被標識爲中毒內存,這個時候訪問這片內存空間一樣會拋出異常。
使用方法:
「Edit Scheme…」 —> 「Run」 —> 「Diagnostics」 —> 「Zombie Objects」
開啓AddressSanitizer以後,在調試程序的過程當中,若是有遇到EXC_BAD_ACCESS錯誤,程序則會自動終止,拋出異常。
LLDB是一個有着 REPL 的特性和 C++ ,Python 插件的開源調試器。LLDB 綁定在 Xcode 內部,存在於主窗口底部的控制檯中。調試器容許你在程序運行的特定時暫停它,你能夠查看變量的值,執行自定的指令,而且按照你所認爲合適的步驟來操做程序的進展。(這裏有一個關於調試器如何工做的整體的解釋。)
你之前有可能已經使用過調試器,即便只是在 Xcode 的界面上加一些斷點。可是經過一些小的技巧,你就能夠作一些很是酷的事情。GDB to LLDB 參考是一個很是好的調試器可用命令的總覽。你也能夠安裝 Chisel,它是一個開源的 LLDB 插件合輯,這會使調試變得更加有趣。
與此同時,讓咱們以在調試器中打印變量來開始咱們的旅程吧。
基礎
這裏有一個簡單的小程序,它會打印一個字符串。注意斷點已經被加在第 8 行。斷點能夠經過點擊 Xcode 的源碼窗口的側邊槽進行建立。
程序會在這一行中止運行,而且控制檯會被打開,容許咱們和調試器交互。那咱們應該打些什麼呢?
help
最簡單命令是 help,它會列舉出全部的命令。若是你忘記了一個命令是作什麼的,或者想知道更多的話,你能夠經過 help來了解更多細節,例如 help print 或者 help thread。若是你甚至忘記了 help 命令是作什麼的,你能夠試試 help help。不過你若是知道這麼作,那就說明你大概尚未忘光這個命令。??
打印值很簡單;只要試試 print 命令:
LLDB 實際上會做前綴匹配。因此你也可使用 prin,pri,或者 p。但你不能使用 pr,由於 LLDB 不能消除和 process 的歧義 (幸運的是 p 並無歧義)。
你可能還注意到了,結果中有個 $0。實際上你可使用它來指向這個結果。試試 print $0 + 7,你會看到 106。任何以美圓符開頭的東西都是存在於 LLDB 的命名空間的,它們是爲了幫助你進行調試而存在的。
expression
若是想改變一個值怎麼辦?你或許會猜 modify。其實這時候咱們要用到的是 expression 這個方便的命令。
這不只會改變調試器中的值,實際上它改變了程序中的值。這時候繼續執行程序,將會打印 42 red balloons。神奇吧。
注意,從如今開始,咱們將會偷懶分別以 p 和 e 來代替 print 和 expression。
什麼是 print 命令
考慮一個有意思的表達式:p count = 18。若是咱們運行這條命令,而後打印 count 的內容。咱們將看到它的結果與 expression count = 18 同樣。
和 expression 不一樣的是,print 命令不須要參數。好比 e -h +17 中,你很難區分究竟是以 -h 爲標識,僅僅執行 +17 呢,仍是要計算 17 和 h 的差值。連字符號確實很讓人困惑,你或許得不到本身想要的結果。
幸運的是,解決方案很簡單。用 -- 來表徵標識的結束,以及輸入的開始。若是想要 -h 做爲標識,就用 e -h -- +17,若是想計算它們的差值,就使用 e -- -h +17。由於通常來講不使用標識的狀況比較多,因此 e -- 就有了一個簡寫的方式,那就是 print。
輸入 help print,而後向下滾動,你會發現:
1
2
|
'print'
is an abbreviation
for
'expression --'
.
(print是 `expression --` 的縮寫)
|
打印對象
嘗試輸入
1
|
p objects
|
輸出會有點囉嗦
1
|
(NSString *) $7 = 0x0000000104da4040 @
"red balloons"
|
若是咱們嘗試打印結構更復雜的對象,結果甚至會更糟
1
2
|
(lldb) p @[ @
"foo"
, @
"bar"
]
(NSArray *) $8 = 0x00007fdb9b71b3e0 @
"2 objects"
|
實際上,咱們想看的是對象的 description 方法的結果。我麼須要使用 -O (字母 O,而不是數字 0) 標誌告訴 expression 命令以 對象 (Object) 的方式來打印結果。
1
2
3
4
|
(lldb) e -O -- $8(
foo,
bar
)
|
幸運的是,e -o -- 有也有個別名,那就是 po (print object 的縮寫),咱們可使用它來進行簡化:
1
2
3
4
5
6
7
8
|
(lldb) po $8(
foo,
bar
)
(lldb) po @
"lunar"
lunar
(lldb) p @
"lunar"
(NSString *) $13 = 0x00007fdb9d0003b0 @
"lunar"
|
打印變量
能夠給 print 指定不一樣的打印格式。它們都是以 print/或者簡化的 p/格式書寫。下面是一些例子:
默認的格式
1
2
|
(lldb) p 16
16
|
十六進制:
1
2
|
(lldb) p/x 16
0x10
|
二進制 (t 表明 two):
1
2
3
4
|
(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000
|
你也可使用 p/c 打印字符,或者 p/s 打印以空終止的字符串 (譯者注:以 '\0' 結尾的字符串)。
這裏是格式的完整清單。
變量
如今你已經能夠打印對象和簡單類型,而且知道如何使用 expression 命令在調試器中修改它們了。如今讓咱們使用一些變量來減小輸入量。就像你能夠在 C 語言中用 int a = 0 來聲明一個變量同樣,你也能夠在 LLDB 中作一樣的事情。不過爲了能使用聲明的變量,變量必須以美圓符開頭。
1
2
3
4
5
6
7
8
9
10
11
|
(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @
"Saturday"
, @
"Sunday"
, @
"Monday"
]
(lldb) p [$array count]
2
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array objectAtIndex:$a] characterAtIndex:0]
error: no known method
'-characterAtIndex:'
; cast the message send to the method's
return
type
error: 1 errors parsing expression
|
悲劇了,LLDB 沒法肯定涉及的類型 (譯者注:返回的類型)。這種事情經常發生,給個說明就行了:
1
2
3
4
|
(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
77
|
變量使調試器變的容易使用得多,想不到吧???
bt
打印調用堆棧信息,使用 bt all命令能夠打印出全部thread的調用棧信息
好比說:call [self.view setBackgroundColor:[UIColor redColor]],使用這個命令來設置view controller的背景色爲紅色
對中文的兼容
有中文時必須使用 [NSString stringWithUTF8String:] 方法,緣由在於lldb的expression parser有一個bug,不兼容非ASCII字符,須要處理一下才行,不然會報錯「An Objective-C constant string's string initializer is not an array」,參考:http://stackoverflow.com/questions/17192505/error-in-breakpoint-condition
類型的問題
出現類型不肯定或類型不匹配的時候,就會報錯,這個時候必須強制轉換才能夠。
好比前文中獲取indexPath的row的方法的表達式:(int)[indexPath row],或者相似下面這種乾脆無返回值 p (void)NSLog(@"%@",[self.view viewWithTag:1001]),或者上文中的 p (char)[[$array objectAtIndex:$a] characterAtIndex:0],這些場景中都須要明確輸出的類型。
找不到方法
注意,使用系統框架對象的屬性時不能使用dot語法,好比下圖中的問題。
改爲以下的格式才行:
流程控制
當你經過 Xcode 的源碼編輯器的側邊槽 (或者經過下面的方法) 插入一個斷點,程序到達斷點時會就會中止運行。
調試條上會出現四個你能夠用來控制程序的執行流程的按鈕。
從左到右,四個按鈕分別是:continue,step over,step into,step out。
第一個,continue 按鈕,會取消程序的暫停,容許程序正常執行 (要麼一直執行下去,要麼到達下一個斷點)。在 LLDB 中,你可使用 process continue 命令來達到一樣的效果,它的別名爲 continue,或者也能夠縮寫爲 c。
第二個,step over 按鈕,會以黑盒的方式執行一行代碼。若是所在這行代碼是一個函數調用,那麼就不會跳進這個函數,而是會執行這個函數,而後繼續。LLDB 則可使用 thread step-over,next,或者 n 命令。
若是你確實想跳進一個函數調用來調試或者檢查程序的執行狀況,那就用第三個按鈕,step in,或者在LLDB中使用 thread step in,step,或者 s 命令。注意,當前行不是函數調用時,next 和 step 效果是同樣的。
大多數人知道 c,n 和 s,可是其實還有第四個按鈕,step out。若是你曾經不當心跳進一個函數,但實際上你想跳過它,常見的反應是重複的運行 n 直到函數返回。其實這種狀況,step out 按鈕是你的救世主。它會繼續執行到下一個返回語句 (直到一個堆棧幀結束) 而後再次中止。
例子
考慮下面一段程序:
假如咱們運行程序,讓它中止在斷點,而後執行下面一些列命令:
1
2
3
4
5
6
7
|
p i
n
s
p i
finish
p i
frame info
|
這裏,frame info 會告訴你當前的行數和源碼文件,以及其餘一些信息;查看 help frame,help thread 和 help process 來得到更多信息。這一串命令的結果會是什麼?看答案以前請先想想。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
(lldb) p i
(int) $0 = 99
(lldb) n
2014-11-22 10:49:26.445 DebuggerDance[60182:4832768] 101 is odd!
(lldb) s
(lldb) p i
(int) $2 = 110
(lldb) finish
2014-11-22 10:49:35.978 DebuggerDance[60182:4832768] 110 is even!
(lldb) p i
(int) $4 = 99
(lldb) frame info
frame
#0: 0x000000010a53bcd4 DebuggerDance`main + 68 at main.m:17
|
它始終在 17 行的緣由是 finish 命令一直運行到 isEven() 函數的 return,而後馬上中止。注意即便它還在 17 行,其實這行已經被執行過了。
Thread Return
調試時,還有一個很棒的函數能夠用來控制程序流程:thread return 。它有一個可選參數,在執行時它會把可選參數加載進返回寄存器裏,而後馬上執行返回命令,跳出當前棧幀。這意味這函數剩餘的部分不會被執行。這會給 ARC 的引用計數形成一些問題,或者會使函數內的清理部分失效。可是在函數的開頭執行這個命令,是個很是好的隔離這個函數,僞造返回值的方式 。
讓咱們稍微修改一下上面代碼段並運行:
1
2
3
4
5
6
|
p i
s
thread
return
NO
n
p even0
frame info
|
看答案前思考一下。下面是答案:
1
2
3
4
5
6
7
8
9
|
(lldb) p i
(int) $0 = 99
(lldb) s
(lldb) thread
return
NO
(lldb) n
(lldb) p even0
(BOOL) $2 = NO
(lldb) frame info
frame
#0: 0x00000001009a5cc4 DebuggerDance`main + 52 at main.m:17
|
源碼地址:Chisel
Chisel擴展了一些列的lldb的命令來幫助iOS開發者調試iOS應用程序。
1.確保終端安裝了Homebrew
2.終端執行命令:brew install chisel 輸入命令後我遇到第一個問題。
遇見這個問題終端執行命令:sudo chown -R ${USER} /Library/Caches/Homebrew/,執行此命令後問題解決。
3.若是沒有第二步的問題,執行命令:brew install chisel後出現以下界面
4.注意看Caveats下面的那兩行,意思是把第二行的文字command script import /usr/local/opt/chisel/libexec/fblldb.py添加到.lldbinit文件中,這時執行命令echo command script import /usr/local/opt/chisel/libexec/fblldb.py >> ~/.lldbinit(粗體文字替換爲你終端Caveats下面的第二行文字)可免去你去找.lldbinit文件,或者.lldbinit文件不出現的煩惱啊。到此步不出意外已經安裝成功。
5.安裝成功後從新啓動Xcode便可。
6.終端下檢查是否安裝成功輸入命令:lldb,而後輸入help,往下翻出現以下界面爲成功
7.Xcode下檢查是否安裝成功,打斷點進入lldb下輸入help,往下翻出現以下界面爲成功
2.內置命令
Chisel 爲lldb提供了新增的便捷命令,是很是實用的命令
這個命令能夠遞歸打印全部的view,並能標示層級,至關於 UIView 的私有輔助方法 [view recursiveDescription]
。 善用使用這個功能會讓你在調試定位問題時省去不少麻煩。
使用示例:
(lldb) pviews view <TestView: 0x18df8070; baseClass = UIControl; frame = (144 9; 126 167); layer = <CALayer: 0x18df8150>> | <UIView: 0x18df81d0; frame = (0 0; 126 126); userInteractionEnabled = NO; layer = <CALayer: 0x18df8240>> | <UIImageView: 0x18df8330; frame = (0 0; 126 126); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x18df83b0>> | <UILabel: 0x18df8460; frame = (0 135; 126 14); text = 'haha'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x18df7fb0>> | | <_UILabelContentLayer: 0x131a3d50> (layer) | <UILabel: 0x18df8670; frame = (0 155; 126 12); text = 'hahaha'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x18df8730>> | | <_UILabelContentLayer: 0x131bea10> (layer) | <UIImageView: 0x18df88d0; frame = (0 9; 28 27); hidden = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x18df8ba0>>
這個命令也是遞歸打印層級,可是不是view,而是viewController。利用它咱們能夠對viewController的結構一目瞭然。 其實蘋果在IOS8也默默的添加了 UIViewController 的一個私有輔助方法 [UIViewController _printHierarchy]
一樣的效果。
預覽效果:
(lldb) pvc <TabBarController: 0x13772fd0; view = <UILayoutContainerView; 0x151b3a30>; frame = (0, 0; 414, 736)> | <UINavigationController: 0x1602b800; view = <UILayoutContainerView; 0x1b00aca0>; frame = (0, 0; 414, 736)> | | <FirstViewController: 0x16029c00; view = <UIView; 0x1b01e1c0>; frame = (0, 0; 414, 736)> | <UINavigationController: 0x138c5200; view = <UILayoutContainerView; 0x1316a080>; frame = (0, 0; 414, 736)> | | <SecondViewController: 0x16030400; view = <UIView; 0x2094b370>; frame = (0, 0; 414, 736)> | | | <SecondChildViewController: 0x15af6000; view = <UIView; 0x18d4e650>; frame = (0, 64; 414, 628)> | <UINavigationController: 0x1383ca00; view = <UILayoutContainerView; 0x13180070>; frame = (0, 0; 414, 736)> | | <ThirdViewController: 0x138ddc00; view = <UIView; 0x18df6650>; frame = (0, 0; 414, 736)> | | | <ThirdChild1ViewController: 0x1393fe00; view = <UIView; 0x131ec000>; frame = (0, 0; 414, 672)> | | | <ThirdChild2ViewController: 0x138dce00; view = <UIView; 0x204075a0>; frame = (414, 0; 414, 672)> | | | <ThirdChild3ViewController: 0x138a8e00; view = <UIView; 0x20426250>; frame = (828, 0; 414, 672)> | <UINavigationController: 0x160eca00; view = <UILayoutContainerView; 0x152f7d90>; frame = (0, 0; 414, 736)> | | <FourViewController: 0x13157cc0; view not loaded>
是否是方便不少呢,並且還能夠看到 viewController 是否已經 viewDidLoad .
這是個頗有意思的功能,它可讓你使用Mac的預覽打開一個 UIImage, CGImageRef, UIView, 或 CALayer。 這個功能或許能夠幫咱們用來截圖、用來定位一個view的具體內容。 可是在我試用了一下,發現暫時仍是隻能在模擬器時使用,真機還不行。
使用簡單:
(lldb) visualize imageView
fv
和 fvc
這兩個命令是用來經過類名搜索當前內存中存在的view和viewController實例的命令,支持正則搜索。
如:
(lldb) fv scrollView 0x18d3b8c0 UIScrollView 0x137d0c50 UIScrollView 0x131b1580 UIScrollView 0x131b2070 UIScrollView (lldb) fvc Home 0x1393fe00 HomeFeedsViewController 0x138a8e00 HomeFeedsViewController (lldb)
這兩個命令用來顯示和隱藏一個指定的 UIView .
(lldb) show self.view (lldb) hide self.view
也可使用內存地址隱藏和現實view,好比經過 fv cate找到一個view後使用hide隱藏它
(lldb)fv cate 0x7fd5b6e06920 AlbumCategoryView (lldb) hide 0x7fd5b6e06920
這兩組命令用來標識一個view或layer的位置時用, mask用來在view上覆蓋一個半透明的矩形, border能夠給view添加邊框。可是在我實際使用的過程當中mask老是會報錯,估計是有bug, 那麼mask/unmask 通常不要用好了,用border命令是同樣的效果,反正兩者的用途都是找到一個對應的view.
若是我要給第二個view添加一個顏色爲藍色,寬度爲2的邊框,以後再用unborder命令移除,操做以下:
經過help border命令知道border的使用格式以下:
Options: --color/-c <color>; Type: string; A color name such as 'red', 'green', 'magenta', etc. --width/-w <width>; Type: CGFloat; Desired width of border. Syntax: border [--color=color] [--width=width] <viewOrLayer>
其中viewOrLayer表示你要修改的view的地址,咱們經過pviews命令知道,第二個view的地址是0x7feae2d605f0,因此咱們輸入
border -c blue -w 2 0x7feae2d605f0 //添加邊框 unborder 0x7feae2d605f0 //移除邊框
注意我在輸入每一個border/unborder命令時,右側模擬器第二個view(紅色view)的變化。
這個命令會從新渲染,便可以從新繪製界面, 至關於執行了 [CATransaction flush]
方法,要注意若是在動畫過程當中執行這個命令,就直接渲染出動畫結束的效果。
當你想在調試界面顏色、座標之類的時候,能夠直接在控制檯修改屬性,而後caflush
就能夠看到效果啦,是否是要比改代碼,而後從新build省事多了呢。
例, 其中 $122 便是目標UIView:
(lldb) p view (long) $122 = 140718754142192 (lldb) e (void)[$122 setBackgroundColor:[UIColor greenColor]] (lldb) caflush
這個命令就是用來打斷點用的了,雖然你們斷點可能都喜歡在圖形界面裏面打,可是考慮一種狀況:咱們想在 [MyViewController viewWillAppear:]
裏面打斷點,可是 MyViewController並無實現 viewWillAppear:
方法, 以往的做法可能就是在子類中實現下viewWillAppear:
,而後打斷點,而後rebuild。
那麼幸虧有了 bmessage
命令。咱們能夠不用這樣就能夠打這個效果的斷點: (lldb) bmessage -[MyViewController viewWillAppear:]
上面命令會在其父類的 viewWillAppear:
方法中打斷點,並添加上了條件:[self isKindOfClass:[MyViewController class]]
3. 自定義命令
咱們也能夠自定義插件,不過前提是要懂一些 python。 好比設計一個打印keyWindow的windowLevel的命令:
建立python腳本文件 /magical/commands/example.py
:
#!/usr/bin/python # Example file with custom commands, located at /magical/commands/example.py import lldb import fblldbbase as fb def lldbcommands(): return [ PrintKeyWindowLevel() ] class PrintKeyWindowLevel(fb.FBCommand): def name(self): return 'pkeywinlevel' def description(self): return 'An incredibly contrived command that prints the window level of the key window.' def run(self, arguments, options): # It's a good habit to explicitly cast the type of all return # values and arguments. LLDB can't always find them on its own. lldb.debugger.HandleCommand('p (CGFloat)[(id)[(id)[UIApplication sharedApplication] keyWindow] windowLevel]')
其中定義了PrintKeyWindowLevel
的類,須要實現 name
description
run
方法來分別告訴名稱、描述、和執行實體。
建立好腳本後,而後在前面安裝時建立的 ~/.lldbinit
文件中添加一行:
script fblldb.loadCommandsInDirectory('/magical/commands/')
而後重啓Xcode以後就可使用自定義的命令啦。
參考連接:
http://www.jianshu.com/p/79468a2eb6db
https://blog.cnbluebox.com/blog/2015/03/05/chisel/
http://www.cocoachina.com/ios/20141219/10709.html
http://www.jianshu.com/p/3e4b10083b4d
http://www.cocoachina.com/ios/20150803/12805.html
http://www.cocoachina.com/ios/20161102/17884.html
http://blog.csdn.net/u013822374/article/details/50963108