LLDB 是 Xcode 中自帶的一個調試工具,在開發的過程當中使用好了這個調試工具,不只是能力的一種提高,更是一種裝逼的 神器。git
一般當程序 crash 或者有斷點的時候,會自動的變成 LLDB 模式。也能夠手動 處理,直接點擊這裏: github
也會變成 LLDB 模式。最終的效果是這樣的:這個指令的意思,能實時的執行代碼中的代碼邏輯。就像下面這樣的:express
當點擊下一步執行的時候,NSLog 打印的值是 CoderHG 而不是 Coder。這個功能想一想都感受挺不錯的。bash
這個指令與 expr 相似,調用一行代碼,形如這樣的:app
call self.view.backgroundColor = [UIColor redColor];工具
其實關於打印,應該全部的小夥伴都知道的。接着上面的步驟,作以下的操做: 佈局
在 LLDB 中有兩個常見的打印指令 p 與 po。學習
對內存的操做,無非就是讀寫操做。 修改內存中的值:測試
memory write 內存地址 數值ui
如:memory write 0x7ffee685dba8 25
讀取內存操做:
memory read/數量 _ 格式 _ 字節數 內存地址
或者
x/數量 _ 格式 _ 字節數 內存地址
如:
memory read/1wx 0x7ffee14a5ba8 memory read/1wd 0x7ffee14a5ba8
寓意是:讀取 0x7ffee14a5ba8 中 4 個字節的內容。 示例以下:
bt 返回全部的調用棧, 形如:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
* frame #0: 0x000000010758b6fd LLDBDev`-[ViewController viewDidLoad](self=0x00007fedad7057e0, _cmd="viewDidLoad") at ViewController.m:27
**中間被我幹掉了不少。**
frame #34: 0x000000010758b79f LLDBDev`main(argc=1, argv=0x00007ffee8674108) at main.m:14
frame #35: 0x000000010c2d1d81 libdyld.dylib`start + 1
frame #36: 0x000000010c2d1d81 libdyld.dylib`start + 1
複製代碼
這個指令很強大,如今的 Xcode 在這裏都顯示不全了:
因此只能藉助 bt 指令。
沒有實戰的紙上談兵,都是耍流氓。 在開始以前,先定義一個 Class,代碼以下所示:
#import <Foundation/Foundation.h>
@interface HGObject : NSObject
/** 年齡 */
@property (nonatomic, assign) NSInteger age;
/** 身高 */
@property (nonatomic, assign) NSInteger height;
@end
#import "HGObject.h"
@implementation HGObject
@end
複製代碼
很簡單的一個Class。
你們都說一個instance 對象中的 isa 的值就是當前 instance 對象的 class 對象的值,接下來求證一下。先寫一段簡單的代碼:
Class cls = NSClassFromString(@"HGObject");
id obj = [[cls alloc] init];
NSLog(@"%@, %@", cls, obj);
複製代碼
運行代碼發現:
看不到任何的地址顯示,因此只能是藉助 LLDB 調試工具,這裏既是是使用簡單的 p 或者 po指令都是不能夠的。須要藉助上面說的 操做內存 的指令。
輕鬆搞定,上圖中是否是就說明了一個 Class 對象的 instance 對象的 isa 的值就是其 class 對象自己的值呢?是的,原本就是這樣的。
簡單的實現以下代碼:
HGObject* obj = [[HGObject alloc] init];
obj.age = 18;
obj.height = 2;
NSLog(@"%@", obj);
複製代碼
在 NSLog 處打一個斷點,運行代碼,打開內存查看視圖:
剛打開是這樣的:
將 obj 的地址寫入,再看下面這張圖:
看到上面的內存圖,發現一個規律,請看下圖:
上圖中的內存分佈,如紅框框所示,分別是 isa,_age 與 _height。爲了驗證其正確性,能夠修改一下其中的值,看一下效果:
上圖中的邏輯大概爲:查找 _age 與 _height 對應的地址,而後修改其地址的值,而後刷新看內存視圖。
顧名思義,給一個內存打一個斷點。其實在開發中,仍是挺實用的,有點像 KVO 監聽。下圖爲簡單的測試代碼,圖中在 viewDidLoad 有一個手動打的一個斷點,目的是進入 lldb 環境:
進入 lldb 環境以後,咱們能夠執行以下指令:
watchpoint set variable self->_name
log 爲:
Watchpoint created: Watchpoint 1: addr = 0x7fcfaf9061d0 size = 8 state = enabled type = w
watchpoint spec = 'self->_name'
new value: 0x0000000000000000
複製代碼
即爲斷點成功。修改 _name的值,即爲修改 _name 所在內存的值,因此應該是這樣的:
值得注意的是,log 能將舊值、新值一塊兒打印出來。固然,設置內存斷點的指令,還能夠這樣:
watchpoint set expression &_name
內存斷點, 在分析數據流轉的特別有用,好比就想知道某個變量在什麼狀況下爲 nil 了。
這個功能可厲害了,先看一個問題:
其實這個就是咱們在作自動佈局是後的一個問題,在控制檯會給出這樣的提示。若是界面簡單,那麼很好排查,若是界面複雜,那麼就很難定位問題所在。那麼如何找到具體的視圖呢?能夠這樣來:
這樣就能實時的定位到是界面上的哪一個UI 了,具體的命令以下:
(lldb) e id $hgView = (id)0x7fdfc66127f0
(lldb) e (void)[$hgView setBackgroundColor:[UIColor redColor]]
(lldb) e (void)[CATransaction flush]
複製代碼
注意:後面的那個命令必定要執行,不然在 lldb 的狀態下是看不到效果的。
看到這個小標題就很詭異,意思就是在代碼運行的過程當中如何去經過修改代碼的方式修改代碼的邏輯。有點繞,先簡單的舉個例子。
有以下的代碼:
主要看 yesOrNo
屬性,當在代碼已經運行起來的時候,想從新運行,就要把 yesOrNo
的值給改爲 true
。你會怎麼作?其實在上面已經介紹了,使用 expr
能夠搞定,可是這裏還有能夠有更高級的。 以下圖所示,弄一個斷點,雙擊讓斷點變成編輯狀態。
寫上 expression yesOrNo = true
會發現這樣的話不用每次運行到這個斷點的時候都去 lldb 一次。
這樣的斷點, 是否是很高大上!??
就想知道某個屬性的值是在何時改變的,應該怎麼辦呢?由於不少的時候,某個屬性的改變會發生在不少的地方,那如何作到統一的跟蹤呢? 在不少年前我是這樣作的:重寫 set
方法,而後在 set
方法中打一個斷點。這樣作是很優秀的,可是也是最不雅觀的。由於重寫了 set
方法以後,還須要再次刪除,太 TM 繁瑣了。未嘗不弄一個斷點呢?再者說,若是去跟蹤系統屬性的變更呢?接下來就介紹一個比較牛逼的方法。以監聽 -[UILabel setText:]
方法爲例。
第一步就是盤她:
而後再這樣的盤她:
-[UILabel setText:]
這一句你們都能看懂。以後是這樣子的:
這樣以後,會驚奇的發現,什麼也沒有幹,這個斷點就觸發了:
是的,這是彙編代碼。原本 UILabel 中的系統方法都是沒有暴露實現的,跳到這樣的界面也是很正常的。這究竟是怎麼回事呢?咱們能夠簡單的來一波下面的操做: 從上面的這張圖中,就能看出端倪。在看到這樣強大的斷點方式的同時,也帶來了不少的思考。這樣打斷點就顯得有點流氓
了,只要是這個方法被觸發的地方都會被斷點到。那麼就看一下下面這張圖:
我只想監聽在 btn1
方法中被觸發的 -[UILabel setText:]
,應該怎麼辦?在實際的開發中, 可能 btn1
方法會很複雜。直接給出最終的處理方案:
這張圖似曾相識。其中關鍵的命令是這樣的 breakpoint set -one-shot true --name "-[UILabel setText:]"
這句命令的大意是若是在 btn1
方法中有 -[UILabel setText:]
操做的話,會被自動觸發跟蹤。
具體這樣的斷點有什麼用,能夠隨便的腦補一下。
一個常見的修復週期就是修改代碼,編譯,從新運行,而且祈禱出現最好的結果。
熟悉一些常見的 LLDB 調試技巧以後,在實際的開發中能夠節省咱們一大堆的調試時間。