LLDB詳解

常用調(diào)試

你常用的調(diào)試是不是這樣光涂?

NSLog(@"%@", whatIsInsideThisThing);

或者寫一個臨時變量日杈?

NSNumber *n = @6; 

或者專門寫個檢查器來判斷遣铝?

if (1 || theBooleanAtStake) { ... }

或者專門寫一個方法佑刷?

int calculateTheTrickyValue {
  return 9;
  /*
   先這樣
   ...
}

是不是每次都要重新運行程序,重新開始翰蠢?

代碼已經(jīng)夠多项乒,你還要多寫幾句代碼啰劲,然后重新編譯運行看看結(jié)果對不對梁沧。如果你用的是低配電腦,或者項目比較大蝇裤,運行等待的過程那真是難受巴⒅А!

其實你不需要重新編譯運行栓辜,你完全可以使用調(diào)試器(也叫控制臺)恋拍,即使你已經(jīng)會使用打個斷點,或打個全局斷點藕甩,但實際上調(diào)試器還可以做更多事情施敢。

接下來我們來重新認識Xcode中的調(diào)試器吧!看看到底能做多少事情狭莱。

LLDB

LLDB是一個帶著REPL(交互式解釋器)僵娃、C++特性,且?guī)в蠵ython插件的開源調(diào)試器腋妙。LLDB 綁定在 Xcode 內(nèi)部默怨,也就是我們常見到的控制臺中(這里有一個關于調(diào)試器如何工作的總體的解釋。)

咱們以前使用調(diào)試器的時候骤素,只是簡單的在Xcode中加一些斷點匙睹,然后通過
(lldb) po value
來打印值對不對?但提到上面的那些缺點济竹,這已經(jīng)不能滿足我們調(diào)試的要求了痕檬。
接下來我們做更酷的事!

基礎

先寫一個簡單的代碼送浊。這里會打印字符串梦谜。然后我們在打印字符串的那行代碼加個斷點。相信這點大家都會罕袋,如果你的確是新手改淑,那你點下圖中22那個數(shù)字,就會看到和圖中一樣的斷點標示浴讯。

斷點

我們運行一下朵夏,接下來編譯器運行到第22行的時候就停住了!
這時我們就可以使用調(diào)試器了 榆纽。先看看怎么用仰猖。
我們輸入: " help "

help

(lldb) help
Debugger commands:
  apropos           -- List debugger commands related to a word or subject.
  breakpoint        -- Commands for operating on breakpoints (see 'help b' for
                       shorthand.)
  bugreport         -- Commands for creating domain-specific bug reports.
  command           -- Commands for managing custom LLDB commands.
  disassemble       -- Disassemble specified instructions in the current
                       target.  Defaults to the current function for the
                       current thread and stack frame.
  expression        -- Evaluate an expression on the current thread.  Displays
                       any returned value with LLDB's default formatting.
  frame             -- Commands for selecting and examing the current thread's
                       stack frames.
  gdb-remote        -- Connect to a process via remote GDB server.  If no host
                       is specifed, localhost is assumed.
  gui               -- Switch into the curses based GUI mode.
  help              -- Show a list of all debugger commands, or give details
                       about a specific command.
....太長就不復制了

然后調(diào)試器打印了一些文字捏肢,這都是調(diào)試器的命令和說明,先簡單的看一看饥侵。

print

我們平時使用最多的就是p了鸵赫,其實它是print,輸入看一下:

打印結(jié)果

實際上LLDB里面搞了很多別名躏升,比如prin辩棒、prip等膨疏,都是print的別名一睁,使用的效果和print是一樣的。但是你不能用pr佃却,因為LLDB不能區(qū)分你輸入的pr是不是process者吁。

圖中打印是不是還有個$0?饲帅,這個東西是用它來指向這個結(jié)果的复凳,比如print $0 + 7也就是計算count+7的值,輸入后的結(jié)果是106灶泵。任何以美元符號開頭的東西都存在與LLDB的命名控件里面育八,它是為了幫助咱們調(diào)試而存在的。

expression

如果要改變一個值丘逸,你可用expression這個命令

改變一個值

你看单鹿,這個家伙它不僅改變了調(diào)試器中的值,它甚至還改變了程序中的值深纲!牛逼吧仲锄。
它也有給別名e

那么現(xiàn)在為了簡單一點湃鹊,從現(xiàn)在開始儒喊,我們用 pe 來代替 printexpression

什么是 print 命令

print就是打印的意思币呵,比如想打印一個變量 print sum
這里有一個很有意思的事情怀愧,你輸入p count = 18expression count = 18,結(jié)果是一樣樣的余赢!

輸入 help print芯义,然后向下滾動,你會發(fā)現(xiàn):

'print' is an abbreviation for 'expression --'.   
(print是 `expression --` 的縮寫) `--` 標示不需要參數(shù)

打印對象

嘗試輸入

p objects

控制臺輸出了

(NSString *) $7 = 0x0000000104da4040 @"red balloons"

如果打印復雜一點的的對象妻柒,你會發(fā)覺只打印了對象的指針地址

(lldb) p @[ @"foo", @"bar" ]

(NSArray *) $8 = 0x00007fdb9b71b3e0 @"2 elements" 

其實我只是想看看這個對象的 description 方法的結(jié)果扛拨。
那我就需要用-O(是字母O)這個參數(shù)了,我們輸入 e -O --$8举塔,就打印了里面的值绑警。

(lldb) e -O -- $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)

其實 e -o -- 也有個別名求泰,那就是 po (print object 的縮寫),咱們用它更簡單计盒】势担看~

(lldb) po $8
<__NSArrayI 0x7fdb9b71b3e0>(
foo,
bar
)
(lldb) po @"lunar"
lunar
(lldb) p @"lunar"
(NSString *) $13 = 0x00007fdb9d0003b0 @"lunar"

還可以打印這些

我們可以設置打印格式,打印格式這么寫:print/<fmt>p/<fmt>
例子:
默認的格式

(lldb) p 16
16

十六進制:

(lldb) p/x 16
0x10

二進制 (t 代表 two):

(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000

更多格式點這里北启。

變量

前面講完打印對象卜朗、又講完修改變量值,現(xiàn)在我們在講點東西暖庄。
我們可以用LLDB定義變量聊替,是不是很酷!
咱們只需要記住培廓,申明變量必須要以美元符開頭〈航校看例子

(lldb) e int $a = 2
(lldb) p $a * 19
(int) $0 = 38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
(NSUInteger) $1 = 3
(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

最后一個怎么回事肩钠!打印不出來?
這其實是因為沒有確定返回的類型值暂殖。
解決辦法很簡單价匠,前面加個類型,就像oc里面的類型強轉(zhuǎn)一樣呛每。

(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
(char) $3 = 'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
(char) $4 = 77

搞定了踩窖。

流程控制

當你通過 Xcode 的源碼編輯器的側(cè)邊槽 (或者通過下面的方法) 插入一個斷點,程序到達斷點時會就會停止運行晨横。

調(diào)試條上會出現(xiàn)四個你可以用來控制程序的執(zhí)行流程的按鈕洋腮。

從左到右,四個按鈕分別是:continue手形,step over啥供,step into,step out库糠。

第一個伙狐,continue 按鈕,會取消程序的暫停瞬欧,允許程序正常執(zhí)行 (要么一直執(zhí)行下去贷屎,要么到達下一個斷點)。在 LLDB 中艘虎,你可以使用 process continue 命令來達到同樣的效果唉侄,它的別名為 continue,或者也可以縮寫為 c顷帖。

第二個美旧,step over 按鈕渤滞,會以黑盒的方式執(zhí)行一行代碼。如果所在這行代碼是一個函數(shù)調(diào)用榴嗅,那么就不會跳進這個函數(shù)妄呕,而是會執(zhí)行這個函數(shù),然后繼續(xù)嗽测。LLDB 則可以使用 thread step-over绪励,next,或者 n 命令唠粥。

如果你確實想跳進一個函數(shù)調(diào)用來調(diào)試或者檢查程序的執(zhí)行情況疏魏,那就用第三個按鈕,step in晤愧,或者在LLDB中使用 thread step in大莫,step,或者 s 命令官份。注意只厘,當前行不是函數(shù)調(diào)用時,nextstep 效果是一樣的舅巷。

大多數(shù)人知道 c羔味,ns,但是其實還有第四個按鈕钠右,step out赋元。如果你曾經(jīng)不小心跳進一個函數(shù),但實際上你想跳過它飒房,常見的反應是重復的運行 n 直到函數(shù)返回搁凸。其實這種情況,step out 按鈕是你的救世主情屹。它會繼續(xù)執(zhí)行到下一個返回語句 (直到一個堆棧幀結(jié)束) 然后再次停止坪仇。

例子

考慮下面一段程序:

代碼.jpg

假如我們運行程序,讓它停止在斷點垃你,然后執(zhí)行下面一些列命令:

p i
n
s
p i
finish
p i
frame info

這里椅文,frame info 會告訴你當前的行數(shù)和源碼文件,以及其他一些信息惜颇;查看 help frame皆刺,help threadhelp process 來獲得更多信息。這一串命令的結(jié)果會是什么凌摄?看答案之前請先想一想羡蛾。

(lldb) p i
(int) $0 = 99
(lldb) n
2021-10-26 23:10:00.101318+0800 LLDBTest[19576:205894] 101 is odd!
(lldb) s
(lldb) p i 
(int) $1 = 110
(lldb) finish
2021-10-26 23:10:17.660446+0800 LLDBTest[19576:205894] 110 is even!
(lldb) p i
(int) $2 = 99
(lldb) frame info
frame #0: 0x000000010e20de3c LLDBTest`-[ViewController viewDidLoad](self=0x00007fb7762059e0, _cmd="viewDidLoad") at ViewController.m:38:5
(lldb) 

它始終在 38 行的原因是 finish 命令一直運行到 isEven() 函數(shù)的 return,然后立刻停止锨亏。注意即使它還在 38 行痴怨,其實這行已經(jīng)被執(zhí)行過了忙干。

Thread Return

調(diào)試時,還有一個很棒的函數(shù)可以用來控制程序流程:thread return 浪藻。它有一個可選參數(shù)捐迫,在執(zhí)行時它會把可選參數(shù)加載進返回寄存器里,然后立刻執(zhí)行返回命令爱葵,跳出當前棧幀施戴。這意味這函數(shù)剩余的部分不會被執(zhí)行。這會給 ARC 的引用計數(shù)造成一些問題萌丈,或者會使函數(shù)內(nèi)的清理部分失效赞哗。但是在函數(shù)的開頭執(zhí)行這個命令,是個非常好的隔離這個函數(shù)辆雾,偽造返回值的方式 肪笋。

讓我們稍微修改一下上面代碼段并運行:

p i
s
thread return NO
n
p even0
frame info

看答案前思考一下。下面是答案:

(lldb) p i
(int) $0 = 99
(lldb) s
(lldb) thread return NO
(lldb) p even0
(BOOL) $2 = NO
(lldb) frame info
frame #0: 0x000000010a475df9 LLDBTest`-[ViewController viewDidLoad](self=0x00007fdf0f00d4e0, _cmd="viewDidLoad") at ViewController.m:43:5
(lldb) 

斷點

我們都把斷點作為一個停止程序運行乾颁,檢查當前狀態(tài)涂乌,追蹤 bug 的方式。但是如果我們改變和斷點交互的方式英岭,很多事情都變成可能。

斷點允許控制程序什么時候停止湿右,然后允許命令的運行诅妹。

想象把斷點放在函數(shù)的開頭,然后用 thread return 命令重寫函數(shù)的行為毅人,然后繼續(xù)吭狡。想象一下讓這個過程自動化,聽起來不錯丈莺,不是嗎划煮?

管理斷點

Xcode 提供了一系列工具來創(chuàng)建和管理斷點。我們會一個個看過來并介紹 LLDB 中等價的命令 (是的缔俄,你可以在調(diào)試器內(nèi)部添加斷點)弛秋。

在 Xcode 的左側(cè)面板,有一組按鈕俐载。其中一個看起來像斷點蟹略。點擊它打開斷點導航,這是一個可以快速管理所有斷點的面板遏佣。

在這里你可以看到所有的斷點 - 在 LLDB 中通過 breakpoint list (或者 br li) 命令也做同樣的事兒挖炬。你也可以點擊單個斷點來開啟或關閉 - 在 LLDB 中使用 breakpoint enable <breakpointID>breakpoint disable <breakpointID>

(lldb) br li
Current breakpoints:
1: file = '/Users/issuser/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 42, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  1.1: where = LLDBTest`-[ViewController viewDidLoad] + 170 at ViewController.m:44:1, address = 0x0000000101f5fdfa, resolved, hit count = 1 

2: file = '/Users/issuser/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 37, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  2.1: where = LLDBTest`-[ViewController viewDidLoad] + 170 at ViewController.m:44:1, address = 0x0000000101f5fdfa, resolved, hit count = 1 
(lldb) br dis 1
1 breakpoints disabled.
(lldb) br li
1: file = '/Users/issuser/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 42, exact_match = 0, locations = 1 Options: disabled 

  1.1: where = LLDBTest`-[ViewController viewDidLoad] + 170 at ViewController.m:44:1, address = 0x0000000101f5fdfa, unresolved, hit count = 1 

2: file = '/Users/issuser/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 37, exact_match = 0, locations = 1, resolved = 1, hit count = 1

  2.1: where = LLDBTest`-[ViewController viewDidLoad] + 170 at ViewController.m:44:1, address = 0x0000000101f5fdfa, resolved, hit count = 1 

(lldb) br del 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) br li
No breakpoints currently set.

創(chuàng)建斷點

在上面的例子中,我們通過在源碼頁面器的滾槽 16 上點擊來創(chuàng)建斷點状婶。你可以通過把斷點拖拽出滾槽意敛,然后釋放鼠標來刪除斷點 (消失時會有一個非诚谙铮可愛的噗的一下的動畫)。你也可以在斷點導航頁選擇斷點草姻,然后按下刪除鍵刪除钓猬。

要在調(diào)試器中創(chuàng)建斷點,可以使用 breakpoint set 命令碴倾。

(lldb) breakpoint set -f ViewController.m -l 25
Breakpoint 5: where = LLDBTest`-[ViewController viewDidLoad] + 66 at ViewController.m:25:15, address = 0x0000000101f5fd92

也可以使用縮寫形式 br逗噩。雖然 b 是一個完全不同的命令 (_regexp-break 的縮寫),但恰好也可以實現(xiàn)和上面同樣的效果跌榔。

(lldb) b ViewController.m:28
Breakpoint 6: where = LLDBTest`-[ViewController viewDidLoad] + 98 at ViewController.m:29:19, address = 0x0000000101f5fdb2

這些斷點會準確的停止在函數(shù)的開始异雁。Objective-C 的方法也完全可以:

(lldb) breakpoint set -F "-[NSArray objectAtIndex:]"
Breakpoint 5: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
(lldb) b -[NSArray objectAtIndex:]
Breakpoint 6: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x000000010ac7a950
(lldb) breakpoint set -F "+[NSSet setWithObject:]"
Breakpoint 7: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820
(lldb) b +[NSSet setWithObject:]
Breakpoint 8: where = CoreFoundation`+[NSSet setWithObject:], address = 0x000000010abd3820

如果想在 Xcode 的UI上創(chuàng)建符號斷點,你可以點擊斷點欄左側(cè)的 + 按鈕僧须,選擇第三個選項

添加.png

這時會出現(xiàn)一個彈出框纲刀,你可以在里面添加例如 testAddAction 這樣的符號斷點。這樣每次調(diào)用這個函數(shù)的時候担平,程序都會停止示绊,不管是你調(diào)用還是蘋果調(diào)用。

如果你 Xcode 的 UI 上右擊任意斷點暂论,然后選擇 "Edit Breakpoint" 的話面褐,會有一些非常誘人的選擇。

這里取胎,斷點已經(jīng)被修改為只有i99 的時候才會停止展哭。你也可以使用 "ignore" 選項來告訴斷點最初的 n次調(diào)用 (并且條件為真的時候) 的時候不要停止。

接下來介紹 'Add Action' 按鈕...

斷點行為 (Action)

上面的例子中闻蛀,你或許想知道每一次到達斷點的時候 i 的值匪傍。我們可以使用 p i 作為斷點行為。這樣每次到達斷點的時候觉痛,都會自動運行這個命令役衡。可以看到它打印 i薪棒,接著打印了自定義的表達式手蝎。下面是在 LLDB 而不是 Xcode 的 UI 中做這些的時候,看起來的樣子盗尸。

(lldb) breakpoint set -F isEven
Breakpoint 4: where = LLDBTest`-[ViewController isEven] + 26 at ViewController.m:68:5, address = 0x000000010f53ae5a
(lldb) breakpoint modify -c 'i == 99' 1
(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> p i
> DONE
(lldb) br li 1
1: file = '/Users/issuser/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 23, exact_match = 0, locations = 1, resolved = 1, hit count = 1
    Breakpoint commands:
      p i

Condition: i == 99

  1.1: where = LLDBTest`-[ViewController viewDidLoad] + 58 at ViewController.m:23:15, address = 0x000000010f53ac7a, resolved, hit count = 1 

接下來說說自動化柑船。

賦值后繼續(xù)運行

看編輯斷點彈出窗口的底部,你還會看到一個選項: "Automatically continue after evaluation actions." 泼各。它僅僅是一個選擇框鞍时,但是卻很強大。選中它,調(diào)試器會運行你所有的命令逆巍,然后繼續(xù)運行及塘。看起來就像沒有執(zhí)行任何斷點一樣 (除非斷點太多锐极,運行需要一段時間笙僚,拖慢了你的程序)。

這個選項框的效果和讓最后斷點的最后一個行為是 continue 一樣灵再。選框只是讓這個操作變得更簡單肋层。調(diào)試器的輸出是:

(lldb) breakpoint set -F isEven
Breakpoint 3: where = LLDBTest`-[ViewController isEven] + 26 at ViewController.m:68:5, address = 0x0000000104e13e5a
(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> continue
> DONE
(lldb) br li 1
1: file = '/Users/issuser/Desktop/LLDBTest/LLDBTest/ViewController.m', line = 23, exact_match = 0, locations = 1, resolved = 1, hit count = 1
    Breakpoint commands:
      continue

  1.1: where = LLDBTest`-[ViewController viewDidLoad] + 58 at ViewController.m:23:15, address = 0x0000000104e13c7a, resolved, hit count = 1 

執(zhí)行斷點后自動繼續(xù)運行,允許你完全通過斷點來修改程序翎迁!你可以在某一行停止栋猖,運行一個 expression 命令來改變變量,然后繼續(xù)運行汪榔。

例子

想想所謂的"打印調(diào)試"技術吧蒲拉,不要這么做:

NSLog(@"%@", whatIsInsideThisThing);

而是用個打印變量的斷點替換 log 語句,然后繼續(xù)運行痴腌。

也不要:

int calculateTheTrickyValue {
  return 9;

  /*
   Figure this out later.
   ...
}

而是加一個使用 thread return 9 命令的斷點雌团,然后讓它繼續(xù)運行。

符號斷點加上 action 真的很強大士聪。你也可以在你朋友的 Xcode 工程上添加一些斷點锦援,并且加上大聲朗讀某些東西的 action“颍看看他們要花多久才能弄明白發(fā)生了什么雨涛。

完全在調(diào)試器內(nèi)運行

在開始舞蹈之前,還有一件事要看一看懦胞。實際上你可以在調(diào)試器中執(zhí)行任何 C/Objective-C/C++/Swift 的命令。唯一的缺點就是不能創(chuàng)建新函數(shù)... 這意味著不能創(chuàng)建新的類凉泄,block躏尉,函數(shù),有虛擬函數(shù)的 C++ 類等等后众。除此之外胀糜,它都可以做。

我們可以申請分配一些字節(jié):

(lldb) e char *$str = (char *)malloc(8)
(lldb) e (void)strcpy($str, "munkeys")
(lldb) e $str[1] = 'o'
(char) $0 = 'o'
(lldb) p $str
(char *) $str = 0x00007fd04a900040 "monkeys"

我們可以查看內(nèi)存 (使用 x 命令)蒂誉,來看看新數(shù)組中的四個字節(jié):

(lldb) x/1c $str
0x7fd04a900040: monk

我們也可以去掉 3 個字節(jié) (x 命令需要斜引號教藻,因為它只有一個內(nèi)存地址的參數(shù),而不是表達式右锨;使用 help x 來獲得更多信息):

(lldb) x/1w `$str + 3`
0x7fd04a900043: keys

做完了之后括堤,一定不要忘了釋放內(nèi)存,這樣才不會內(nèi)存泄露。(哈悄窃,雖然這是調(diào)試器用到的內(nèi)存):

(lldb) e (void)free($str)

不用斷點調(diào)試

程序運行時讥电,Xcode 的調(diào)試條上會出現(xiàn)暫停按鈕,而不是繼續(xù)按鈕:

點擊按鈕會暫停 app (這會運行 process interrupt 命令轧抗,因為 LLDB 總是在背后運行)恩敌。這會讓你可以訪問調(diào)試器,但看起來可以做的事情不多横媚,因為在當前作用域沒有變量纠炮,也沒有特定的代碼讓你看。

這就是有意思的地方灯蝴。如果你正在運行 iOS app恢口,你可以試試這個: (因為全局變量是可訪問的)

(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x7fe9cc10a240; frame = (0 0; 390 844); gestureRecognizers = <NSArray: 0x600002702430>; layer = <UIWindowLayer: 0x60000273a970>>
   | <UITransitionView: 0x7fe9c9f08b40; frame = (0 0; 390 844); autoresize = W+H; layer = <CALayer: 0x60000296df00>>
   |    | <UIDropShadowView: 0x7fe9cc10b180; frame = (0 0; 390 844); autoresize = W+H; layer = <CALayer: 0x600002976860>>
   |    |    | <UIView: 0x7fe9cc10bca0; frame = (0 0; 390 844); autoresize = W+H; layer = <CALayer: 0x6000029767e0>>
   |    |    |    | <UILabel: 0x7fe9cc10c010; frame = (171 424; 48 24); text = 'LLDB'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000a78c30>>

你可以看到整個層次。Chisel(Chisel是一個LLDB命令集合绽乔,用于幫助調(diào)試iOS應用程序弧蝇。)中 pviews 就是這么實現(xiàn)的。

更新UI

有了上面的輸出折砸,我們可以獲取這個 view:

(lldb) e id $myView = (id)0x7f82b1d01fd0

然后在調(diào)試器中改變它的背景色:

(lldb) e (void)[$myView setBackgroundColor:[UIColor blueColor]]

但是只有程序繼續(xù)運行之后才會看到界面的變化看疗。因為改變的內(nèi)容必須被發(fā)送到渲染服務中,然后顯示才會被更新睦授。

渲染服務實際上是一個另外的進程 (被稱作 backboardd)两芳。這就是說即使我們正在調(diào)試的內(nèi)容所在的進程被打斷了,backboardd 也還是繼續(xù)運行著的去枷。

這意味著你可以運行下面的命令怖辆,而不用繼續(xù)運行程序:

(lldb) e (void)[CATransaction flush]

即使你仍然在調(diào)試器中,UI 也會在模擬器或者真機上實時更新删顶。Chisel 為此提供了一個別名叫做 caflush竖螃,這個命令被用來實現(xiàn)其他的快捷命令,例如 hide <view>逗余,show <view> 以及其他很多命令特咆。所有 Chisel 的命令都有文檔,所以安裝后隨意運行 help show 來看更多信息录粱。

Push 一個 View Controller

想象一個以 UINavigationController 為 root ViewController 的應用腻格。你可以通過下面的命令,輕松地獲取它:

(lldb) e id $nvc = [[[UIApplication sharedApplication] keyWindow] rootViewController]

然后 push 一個 child view controller:

(lldb) e id $vc = [UIViewController new]
(lldb) e (void)[[$vc view] setBackgroundColor:[UIColor yellowColor]]
(lldb) e (void)[$vc setTitle:@"Yay!"]
(lldb) e (void)[$nvc pushViewContoller:$vc animated:YES]

最后運行下面的命令:

(lldb) caflush // e (void)[CATransaction flush]

navigation Controller 就會立刻就被 push 到你眼前啥繁。

查找按鈕的 target

想象你在調(diào)試器中有一個 $myButton 的變量菜职,可以是創(chuàng)建出來的,也可以是從 UI 上抓取出來的旗闽,或者是你停止在斷點時的一個局部變量酬核。你想知道蜜另,按鈕按下的時候誰會接收到按鈕發(fā)出的 action。非常簡單:

(lldb) po [$myButton allTargets]
{(
    <ViewController: 0x7fb58bd2e240>
)}
(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
<__NSArrayM 0x7fb58bd2aa40>(
_handleTap:
)

LLDB 和 Python

LLDB 有內(nèi)建的愁茁,完整的 Python 支持蚕钦。在LLDB中輸入 script,會打開一個 Python REPL鹅很。你也可以輸入一行 python 語句作為 script 命令 的參數(shù)嘶居,這可以運行 python 語句而不進入REPL:

(lldb) script import os
(lldb) script os.system("open http://www.objc.io/")

這樣就允許你創(chuàng)造各種酷的命令。把下面的語句放到文件 ~/myCommands.py 中:

def caflushCommand(debugger, command, result, internal_dict):
  debugger.HandleCommand("e (void)[CATransaction flush]")

然后再 LLDB 中運行:

command script import ~/myCommands.py

或者把這行命令放在 /.lldbinit 里促煮,這樣每次進入 LLDB 時都會自動運行幌墓。Chisel 其實就是一個 Python 腳本的集合脑溢,這些腳本拼接 (命令) 字符串 ,然后讓 LLDB 執(zhí)行。很簡單秫逝,不是嗎宪摧?

緊握調(diào)試器這一武器

LLDB 可以做的事情很多台腥。大多數(shù)人習慣于使用 p烫止,pon疾棵,sc戈钢,但實際上除此之外,LLDB 可以做的還有很多是尔。掌握所有的命令 (實際上并不是很多)殉了,會讓你在揭示代碼運行時的運行狀態(tài),尋找 bug拟枚,強制執(zhí)行特定的運行路徑時獲得更大的能力薪铜。你甚至可以構(gòu)建簡單的交互原型 - 比如要是現(xiàn)在以 modal 方式彈出一個 View Controller 會怎么樣?使用調(diào)試器恩溅,一試便知隔箍。

這篇文章是為了想你展示 LLDB 的強大之處,并且鼓勵你多去探索在控制臺輸入命令脚乡。熟悉LLDB鞍恢,能給你增加更多加分技能。以下是LLDB更多相關內(nèi)容:

    1. 高級lldb操作與lldbinit文件
    1. 代碼模擬lldb執(zhí)行流程
    1. lldb接口學習
    1. 自定義lldb命令

這些都可以在了解LLDB后去做研究每窖。打開 LLDB,輸入 help弦悉,看一看列舉的命令窒典。你嘗試過多少?用了多少稽莉?但愿 NSLog 看起來不再那么吸引你去用瀑志,每次編輯再運行并不有趣而且耗時。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市劈猪,隨后出現(xiàn)的幾起案子昧甘,更是在濱河造成了極大的恐慌,老刑警劉巖战得,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件充边,死亡現(xiàn)場離奇詭異,居然都是意外死亡常侦,警方通過查閱死者的電腦和手機浇冰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聋亡,“玉大人肘习,你說我怎么就攤上這事∑戮螅” “怎么了漂佩?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長罪塔。 經(jīng)常有香客問我投蝉,道長,這世上最難降的妖魔是什么垢袱? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任墓拜,我火速辦了婚禮,結(jié)果婚禮上请契,老公的妹妹穿的比我還像新娘咳榜。我一直安慰自己,他們只是感情好爽锥,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布涌韩。 她就那樣靜靜地躺著,像睡著了一般氯夷。 火紅的嫁衣襯著肌膚如雪臣樱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天腮考,我揣著相機與錄音雇毫,去河邊找鬼。 笑死踩蔚,一個胖子當著我的面吹牛棚放,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播馅闽,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼飘蚯,長吁一口氣:“原來是場噩夢啊……” “哼馍迄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起局骤,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤攀圈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后峦甩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赘来,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年穴店,在試婚紗的時候發(fā)現(xiàn)自己被綠了撕捍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡泣洞,死狀恐怖忧风,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情球凰,我是刑警寧澤狮腿,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站呕诉,受9級特大地震影響缘厢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜甩挫,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一贴硫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伊者,春花似錦英遭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至法精,卻和暖如春多律,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搂蜓。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工狼荞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帮碰。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓粘秆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親收毫。 傳聞我的和親對象是個殘疾皇子攻走,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Xcode po 是什么你以前調(diào)試是不是這樣打印的? 或者臨時寫一個臨時變量此再? 或者專門寫個檢查器來判斷昔搂? 或者專...
    Hanfank閱讀 12,034評論 0 16
  • 你是否曾經(jīng)苦惱于理解你的代碼,而去嘗試打印一個變量的值输拇? NSLog(@"%@", whatIsInsideThi...
    木易林1閱讀 955評論 0 4
  • 你是否曾經(jīng)苦惱于理解你的代碼摘符,而去嘗試打印一個變量的值? NSLog(@"%@", whatIsInsideThi...
    F麥子閱讀 1,252評論 1 2
  • 你是否曾經(jīng)苦惱于理解你的代碼策吠,而去嘗試打印一個變量的值逛裤? NSLog(@"%@", whatIsInsideThi...
    paraneaeee閱讀 1,192評論 0 7
  • 你是否曾經(jīng)苦惱于理解你的代碼,而去嘗試打印一個變量的值猴抹? NSLog(@"%@", whatIsInsideThi...
    我是啊梁閱讀 804評論 1 1