不管你使用的是Swift, Objective-C, C++, C,或者其他的編程語(yǔ)言, 你都需要學(xué)習(xí)如何創(chuàng)建一個(gè)斷點(diǎn).在Xcode這樣的GUI程序中, 在編輯界面的左邊點(diǎn)一下創(chuàng)建一個(gè)斷點(diǎn)是非常簡(jiǎn)單的, 但是在LLDB控制臺(tái)中可以讓你更靈活的控制斷點(diǎn).
在本章中, 你將會(huì)學(xué)到LLDB中所有關(guān)于斷點(diǎn)的知識(shí).
Signals
在本章中, 你將會(huì)使用我提供的一個(gè)工程; 這個(gè)工程的名字叫做Signals, 你可以在資源文件夾里面找到它.
用Xcode打開(kāi)Signals項(xiàng)目.Signals是一個(gè)以美式橄欖球項(xiàng)目為主題的APP, 顯示一些叫做nerdily的攻擊型打法.
在程序內(nèi)部, 這個(gè)項(xiàng)目會(huì)監(jiān)測(cè)幾個(gè)Unix signals并在Signals程序收到這些信號(hào)的時(shí)候顯示這些信號(hào).
Unix signals
是進(jìn)程之間通信的基本形式. 例如, signals中的SIGSTOP
可以保存一個(gè)進(jìn)程的狀態(tài)并且暫停進(jìn)程的執(zhí)行.與之對(duì)應(yīng)的是SIGCONT
, 他告訴程序繼續(xù)執(zhí)行.這兩個(gè)signals都可以被調(diào)試器用來(lái)暫停或繼續(xù)程序的執(zhí)行.
在前面這些章節(jié)中這算是一個(gè)有趣的程序, 因?yàn)樗粌H用來(lái)瀏覽Unix 處理signal, 而且會(huì)高亮顯示(LLDB)控制的線(xiàn)程在處理經(jīng)過(guò)的Unix signals時(shí)發(fā)生了什么.默認(rèn)情況下, LLDB在處理不同的signals時(shí)有自定義的動(dòng)作.當(dāng)附加了LLDB以后有些signals是不會(huì)經(jīng)過(guò)被控制的線(xiàn)程的.
為了顯示signal
, 你既可以在應(yīng)用程序中增加一個(gè)Signal, 也可以在外部的應(yīng)用程序中發(fā)送一個(gè)signal
, 比如在Terminal中.
此外, 這里有一個(gè)UISwitch
可以切換處理signal的代碼塊, 代碼塊的名字叫做sigprocmask
來(lái)啟用或者禁用signal
的處理.
最后, Signal應(yīng)用程序還有在應(yīng)用程序里增加SIGSTOP
的Timeout按鈕, 從根本上凍結(jié)應(yīng)用程序.然而, 如果LLDB附加到了Signals應(yīng)用程序上 (并且在默認(rèn)情況下就是這樣做的, 當(dāng)你通過(guò)Xcode來(lái)構(gòu)建和運(yùn)行Signals的時(shí)候), 調(diào)用SIGSTOP
可以讓你在Xcode里用LLDB檢查線(xiàn)程的至此NG狀態(tài).
確保選中的是iPhone 7 Simulator. 構(gòu)建并運(yùn)行這個(gè)APP.一旦項(xiàng)目運(yùn)行起來(lái)了以后, 在Xcode的控制臺(tái)里找到并暫停調(diào)試器.
繼續(xù)運(yùn)行Xcode并留意觀察模擬器.當(dāng)調(diào)試器暫停然后繼續(xù)執(zhí)行的時(shí)候一行新數(shù)據(jù)會(huì)被添加到UITableView上. 這是Signals監(jiān)測(cè)器監(jiān)測(cè)到Unix signal的SIGSTOP事件時(shí)添加的一個(gè)數(shù)據(jù).當(dāng)線(xiàn)程被停止的時(shí)候, 任何新的
signals
都不會(huì)被立即處理, 因?yàn)榭梢哉J(rèn)為程序 已經(jīng)停止了.
Xcode斷點(diǎn)
在你學(xué)完之前, LLDB控制臺(tái)的斷點(diǎn)會(huì)是一個(gè)亮點(diǎn), 非常值得用來(lái)替代你再Xode中學(xué)到的斷點(diǎn);
Symbolic breakpoints 是一個(gè)Xcode中一個(gè)非常好的調(diào)試功能.它允許你在應(yīng)用程序中的某些symbol處設(shè)置斷點(diǎn).一個(gè)非常簡(jiǎn)單的例子就是-[NSObject init]
, 用的是NSObject實(shí)例的 init
方法.
在Xcode中可以靈活的使用symbolic
斷點(diǎn), 一旦你創(chuàng)建了一個(gè)斷點(diǎn), 下次你啟動(dòng)應(yīng)用程序的時(shí)候可以不用重新輸入.
現(xiàn)在你將會(huì)用symbolic斷點(diǎn)來(lái)顯示出所有被創(chuàng)建的NSObject的實(shí)例.
如果APP正在運(yùn)行那么就殺掉APP的進(jìn)程.然后切換到Breakpoint導(dǎo)航欄.在左下角點(diǎn)擊+按鈕, 并選擇 Symbolic Breakpoint選項(xiàng).
會(huì)出現(xiàn)一個(gè)彈窗.在彈窗的Symbol部分輸入:
-[NSObject init]
.在Action下面, 選擇Add Action
然后在下拉選項(xiàng)中選擇Debugger Command
, 接下來(lái)在輸入框中輸入po [$arg1 class]
.最后,選擇
Automatically continue after evaluating actions
. 你的彈窗看起來(lái)應(yīng)該是下面的樣子:
構(gòu)建并運(yùn)行APP.當(dāng)Signals程序運(yùn)行的時(shí)候Xcode將會(huì)在控制臺(tái)輸出初始化的所有的類(lèi)名, 你會(huì)看到非常的多.
在這里你設(shè)置了一個(gè)
-[NSObject init]
每調(diào)用一次都會(huì)觸發(fā)的斷點(diǎn).當(dāng)斷點(diǎn)觸發(fā)的時(shí)候LLDB會(huì)運(yùn)行一條指令, 并自動(dòng)讓?xiě)?yīng)用程序繼續(xù)執(zhí)行.
注意: 在第十章“Assembly, Registers and Calling Convention”中你將會(huì)學(xué)到如果恰當(dāng)?shù)氖褂煤筒倏v寄存器, 但是現(xiàn)在只需要簡(jiǎn)單的知道`$arg1`與寄存器中的`$rdi`的同義詞, 可以簡(jiǎn)單的看作是調(diào)用`init`方法的類(lèi)的一個(gè)實(shí)例.
如果你完成了查看所有類(lèi)名的提取操作, 然后用在breakpoint導(dǎo)航欄中點(diǎn)擊右鍵選擇刪除斷點(diǎn)的方法刪除斷點(diǎn).
除了symbolic斷點(diǎn), Xcode還支持幾種其他錯(cuò)誤類(lèi)型的斷點(diǎn).其中有一個(gè)叫Exception Breakpoint
.有時(shí)候你的程序會(huì)拋出一些錯(cuò)誤,這些錯(cuò)誤會(huì)導(dǎo)致崩潰.你的第一反應(yīng)應(yīng)該就是啟用exception breakpoint
. Xcode將會(huì)自動(dòng)定位到出問(wèn)題的那行代碼, 這對(duì)于捕獲崩潰有很大的幫助.
最后, 還有一個(gè)斷點(diǎn)類(lèi)型是Swift Error Breakpoint
.這種類(lèi)型的斷點(diǎn)本質(zhì)上是在在swift_willThrow
方法中創(chuàng)建一個(gè)斷點(diǎn)并在swift拋出異常的時(shí)候觸發(fā).在我們使用那些容易出錯(cuò)的APIs的時(shí)候這是我們可以用的一個(gè)非常好的選項(xiàng).它可以讓你在沒(méi)有對(duì)代碼的對(duì)錯(cuò)做出判斷的時(shí)候快速的診斷出現(xiàn)的問(wèn)題.
LLDB斷點(diǎn)語(yǔ)法
現(xiàn)在你已經(jīng)掌握了Xcode中調(diào)試崩潰功能的課程, 是時(shí)候?qū)W習(xí)一下如何通過(guò)LLDB控制臺(tái)創(chuàng)建斷點(diǎn)了. 為了創(chuàng)建有用的斷點(diǎn), 你需要學(xué)習(xí)一下如何查詢(xún)你要找的東西.
image
命令是一個(gè)查看內(nèi)部詳情的極其有用的工具, 對(duì)于設(shè)置斷點(diǎn)來(lái)說(shuō)是至關(guān)重要的.
本書(shū)中在搜索代碼的時(shí)候有兩個(gè)至關(guān)重要的配置.第一個(gè)就是:
(lldb) image lookup -n "-[UIViewController viewDidLoad]"
這個(gè)命令可以提取出-[UIViewController viewDidLoad]
函數(shù)的加載地址.-n
參數(shù)告訴LLDB可以查找symbol或者函數(shù)名.輸出的可能是下面這個(gè)樣子:
1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/
iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/
Library/Frameworks/UIKit.framework/UIKit:
Address: UIKit[0x00000000001c67c8] (UIKit.__TEXT.__text +
1854120)
Summary: UIKit`-[UIViewController viewDidLoad]
另外一個(gè)與之相似并非常有用的命令是:
(lldb) image lookup -rn test
這條命用來(lái)查找test
這個(gè)單詞的而且是區(qū)分大小寫(xiě)的.如果小寫(xiě)的單詞test
在當(dāng)前的可執(zhí)行文件中加載的任何模塊中的任何函數(shù)里(例如:UIKit, Foundation, Core Data, 等等) 找到了, 這條命令都會(huì)輸出出來(lái).
注意:當(dāng)你想要提取匹配到的內(nèi)容的參數(shù)時(shí)候使用`-n`選項(xiàng)(如果你查詢(xún)的內(nèi)容中包含空格則需要用引號(hào)引起來(lái)).`-n`只能幫助你提取出匹配到的斷點(diǎn)的準(zhǔn)確的參數(shù), 尤其是處理swift的時(shí)候, 同時(shí)`-rn`選項(xiàng)在本書(shū)中將會(huì)經(jīng)常用到, 因?yàn)槟愫芸炀蜁?huì)發(fā)現(xiàn)一個(gè)漂亮的正則表達(dá)式可以減少許多輸入.
Objective-C的屬性
學(xué)習(xí)如何在代碼中查詢(xún)加載的代碼是學(xué)習(xí)如何創(chuàng)建斷點(diǎn)的最終目標(biāo).
無(wú)論是Objective-C 還是Swift代碼在被編譯器創(chuàng)建的時(shí)候都有指定的屬性簽名, 我們需要使用不同的斷點(diǎn)策略.
例如在Signals項(xiàng)目中下面的Objective-C類(lèi)聲明了一個(gè)屬性:
@interface TestClass : NSObject
@property (nonatomic, strong) NSString *name;
@end
編譯器將會(huì)為這個(gè)屬性創(chuàng)建getter
方法和setter
方法.
getter看起來(lái)是這個(gè)樣子:
-[TestClass name]
setter
方法看起來(lái)是這個(gè)樣子:
-[TestClass setName:]
構(gòu)建并運(yùn)行APP, 然后暫停調(diào)試器. 接下來(lái)在LLDB中鍵入以下命令驗(yàn)證一下這些方法是存在的:
(lldb) image lookup -n "-[TestClass name]"
在控制臺(tái)中你將會(huì)看到下面這些輸出:
1 match found in /Users/derekselander/Library/Developer/Xcode/
DerivedData/Signals-bqrjxlceauwfuihjesxmgfodimef/Build/Products/Debug-
iphonesimulator/Signals.app/Signals:
Address: Signals[0x0000000100001470] (Signals.__TEXT.__text + 0)
Summary: Signals`-[TestClass name] at TestClass.h:28
LLDB將會(huì)提取出可執(zhí)行文件中包含的函數(shù)的信息.這些代碼看起來(lái)有點(diǎn)嚇人, 但是卻有一些有用的信息在里面.
這些輸出告訴你LLDB在Signals可執(zhí)行文件中能夠找到這些函數(shù)的實(shí)現(xiàn).并且在__TEXT
分段中準(zhǔn)確的偏移量是0x0000000100001470
.LLDB還告訴我們這個(gè)方法生命在TestClass.h
文件中的第28行.
你同樣可以用下面的方法查看setter
方法:
(lldb) image lookup -n "-[TestClass setName:]"
你應(yīng)該會(huì)得到和之前類(lèi)似的輸出, 這一次顯示的是name
這個(gè)屬性setter
方法的實(shí)現(xiàn)和實(shí)現(xiàn)的地址.
Swift 屬性
在Swift中聲明屬性的語(yǔ)法有些不同. 看一下SwiftTestClass.swift
文件你會(huì)發(fā)現(xiàn)下面的代碼:
class SwiftTestClass: NSObject {
var name: String!
}
確保Signals項(xiàng)目正在運(yùn)行并且暫停在LLDB中. 你可以按下Command + K
快速的清空LLDB的控制臺(tái).
在LLDB控制臺(tái)中輸入以下命令:
(lldb) image lookup -rn Signals.SwiftTestClass.name.setter
你將會(huì)得到一些類(lèi)似下面的輸出:
2 matches found in /Users/derekselander/Library/Developer/Xcode/
DerivedData/Signals-bqrjxlceauwfuihjesxmgfodimef/Build/Products/Debug-
iphonesimulator/Signals.app/Signals:
Address: Signals[0x000000010000aba0] (Signals.__TEXT.__text +
38704)
Summary: Signals`@objc Signals.SwiftTestClass.name.setter :
Swift.ImplicitlyUnwrappedOptional<Swift.String> at SwiftTestClass.swift
Address: Signals[0x000000010000ac60] (Signals.__TEXT.__text + 38896)
Summary: Signals`Signals.SwiftTestClass.name.setter :
Swift.ImplicitlyUnwrappedOptional<Swift.String> at SwiftTestClass.swift
在輸出中找到Summary這個(gè)單詞.這里有兩個(gè)有趣的事情需要注意.
首先, 有兩個(gè)symbols被發(fā)現(xiàn)了.第一個(gè)和第二個(gè)有著同樣的名字;然而, 第一個(gè)有@objc
的前綴. 這是編譯器加上去的特定的函數(shù)是用來(lái)作為一個(gè)橋接函數(shù)的. 這有助于swift和Objective-C的混編.
第二, 你看到了函數(shù)名字是多么的長(zhǎng)嗎?要?jiǎng)?chuàng)建一個(gè)有效的Swift斷點(diǎn)就需要這么一個(gè)完整的名字.
如果你想在這個(gè)setter
方法中設(shè)置一個(gè)斷點(diǎn), 你需要按照下面的方法做:
(lldb) b Signals.SwiftTestClass.name.setter :
Swift.ImplicitlyUnwrappedOptional<Swift.String>
使用正則表達(dá)式可以減少大量的輸入.
撇開(kāi)你產(chǎn)生的swift函數(shù)名的長(zhǎng)度不說(shuō), 注意一下Swift的屬性的形式.在屬性name
函數(shù)的簽名中單詞setter
緊跟在屬性的后面. 也許getter
方法也有著同樣的格式?
嘗試捕獲一下SwiftTestClass
類(lèi)的name
屬性的getter
和setter
方法, 與此同時(shí), 使用下面的正則表達(dá)式來(lái)查詢(xún):
(lldb) image lookup -rn Signals.SwiftTestClass.name
這條命令使用正則表達(dá)式查詢(xún)并提取Signals.SwiftTestClass.name
中的一切.
由于這是一個(gè)正則表達(dá)式, 所以.
是一個(gè)通配符, 同時(shí)在函數(shù)簽名單重又用來(lái)匹配點(diǎn)語(yǔ)法.
你會(huì)得到一些合理的輸出, 但是每一次在控制臺(tái)的輸出中你都會(huì)看到Summary
.你會(huì)發(fā)現(xiàn)輸出匹配了getter
, (Signals.SwiftTestClass.name.getter) , setter
,(Signals.SwiftTestClass.name.setter), @objc對(duì)應(yīng)的橋接符, 以及一個(gè)包含materializeForSet
的方法, 這個(gè)方法你再后面會(huì)學(xué)到.
下面是Swift屬性函數(shù)名的樣本:
ModuleName.Classname.PropertyName.(getter|setter)
提取方法,找到樣本和縮小你搜索范圍的能力, 是在你代碼中創(chuàng)建智能斷點(diǎn)解開(kāi)Swift/Objective-C語(yǔ)言?shī)W秘的非常偉大的途徑.
最后...創(chuàng)建斷點(diǎn)
現(xiàn)在你已經(jīng)知道了如何在你的代碼中查詢(xún)已經(jīng)存在的函數(shù)和方法, 是時(shí)候在這些方法上創(chuàng)建斷點(diǎn)了.
如果你的Signals項(xiàng)目正在運(yùn)行, 停止并重啟這個(gè)程序, 然后點(diǎn)擊暫停按鈕停止應(yīng)用程序并進(jìn)入LLDB控制臺(tái).
有幾種不同的方法創(chuàng)建斷點(diǎn).最簡(jiǎn)單的方法是輸入小寫(xiě)字母b
緊跟著后面是函數(shù)名.這在Objective-C 和 C當(dāng)中非常的簡(jiǎn)單, 因?yàn)樗拿趾芏桃埠苋菀纵斎?例如, -[NSObject init]). 而在C++和swift中卻十分復(fù)雜, 因?yàn)榫幾g器會(huì)為函數(shù)返回給你一個(gè)相當(dāng)長(zhǎng)的名字.
鑒于UIKit主要是用Objective-C
(至少在寫(xiě)這本書(shū)的時(shí)候是這樣實(shí)現(xiàn)的), 嘗試用 b
參數(shù)創(chuàng)建一個(gè)斷點(diǎn), 像下面這樣:
(lldb) b -[UIViewController viewDidLoad]
你會(huì)看到下面這些輸出:
Breakpoint 1: where = UIKit`-[UIViewController viewDidLoad], address =
0x0000000102bbd788
每當(dāng)你創(chuàng)建了一個(gè)有效的斷點(diǎn), 控制臺(tái)都會(huì)輸出一些關(guān)于這個(gè)斷點(diǎn)的信息.
在這次特定的情況下, 這個(gè)斷點(diǎn)作為Breakpoint 1
被創(chuàng)建, 因?yàn)樵谶@個(gè)特定的調(diào)試會(huì)話(huà)中這是創(chuàng)建的第一個(gè)斷點(diǎn).當(dāng)你創(chuàng)建更多斷點(diǎn)的時(shí)候, 斷點(diǎn)ID會(huì)不斷的增長(zhǎng).
繼續(xù)運(yùn)行調(diào)試器, 一旦你繼續(xù)執(zhí)行一個(gè)新的SIGSTOP
會(huì)被顯示出來(lái).在單元格上點(diǎn)擊進(jìn)入詳情的UIViewController.在詳情視圖控制器調(diào)用viewDidLoad
的時(shí)候程序應(yīng)該會(huì)暫停.
注意:像許多快捷命令一樣, `b`是LLDB命令中一個(gè)長(zhǎng)單詞的縮寫(xiě). 你可以自己運(yùn)行help b 命令查看它的幫助, 并學(xué)習(xí)它很酷的技巧.
除了b
命令以外,還有一個(gè)長(zhǎng)的breakpoint set
命令, 這個(gè)命令有很多可用的選項(xiàng). 你在后面的幾個(gè)章節(jié)中會(huì)用到它. 許多命令都是breakpoint set
命令的分支.
正則斷點(diǎn)和范圍
另一個(gè)極其有用的命令是正則表達(dá)式斷點(diǎn)rbreak
是breakpoint set -r %1
.你可以用智能的正則表達(dá)式斷點(diǎn)快速的在你想要停下來(lái)的許多地方創(chuàng)建斷點(diǎn).
讓我們回頭看看之前的很長(zhǎng)的那個(gè)swift屬性name
函數(shù)的例子, 與下面的輸入不同的是:
(lldb) b Breakpoints.SwiftTestClass.name.setter :
Swift.ImplicitlyUnwrappedOptional<Swift.String>
你可以輸入:
(lldb) rb SwiftTestClass.name.setter
盡管上面的命令更短一點(diǎn), 但是也有一個(gè)煩人的地方.這個(gè)斷點(diǎn)也會(huì)捕捉到Objective-C橋接的name
屬性的setter
方法, 當(dāng)這個(gè)方法被調(diào)用的時(shí)候, 會(huì)強(qiáng)制停止兩次.
你可以給這個(gè)斷點(diǎn)添加一個(gè)參數(shù)^(@).*
, 這個(gè)參數(shù)的本質(zhì)是說(shuō)"過(guò)濾掉以@xxx開(kāi)始的函數(shù)".在未來(lái)的幾個(gè)章節(jié)中, 你將構(gòu)建一個(gè)執(zhí)行正則搜索的命令自動(dòng)過(guò)濾掉這些橋接函數(shù).
從現(xiàn)在開(kāi)始你只需要處理兩個(gè)斷點(diǎn). 另一種更加簡(jiǎn)潔的寫(xiě)法是, 下面這種形式:
(lldb) rb name\.setter
這會(huì)在任何包含name.setter
的地方創(chuàng)建一個(gè)斷點(diǎn).這在你知道項(xiàng)目里沒(méi)有其他地方swift屬性的明知叫做name
的時(shí)候是行的通的.否則的話(huà)你就會(huì)在在每一處都創(chuàng)建一個(gè)斷點(diǎn).
現(xiàn)在嘗試在UIViewController
的每一個(gè)Objective-C 的實(shí)例方法里創(chuàng)建一個(gè)斷點(diǎn). 我們可以在LLDB中輸入下面的指令:
(lldb) rb '\-\[UIViewController\ '
反斜杠是轉(zhuǎn)義字符用來(lái)指明你再正則表達(dá)式中想要搜索的原意字符.那么結(jié)果就是, 這條指令會(huì)在每一個(gè)字符串里包含-[UIViewController
后面跟著一個(gè)空格的地方停下來(lái).
等一下...在Objective-C的categories
里是怎樣的呢?他們提供了一種(-|+)[ClassName(categoryName) method]
的形式.你可以用包含categories的形式重寫(xiě)正則表達(dá)式.
在LLDB會(huì)話(huà)中輸入以下命令, 并在提示用輸入y
進(jìn)行確認(rèn):
(lldb) breakpoint delete
這條命令會(huì)刪除你設(shè)置的所有的斷點(diǎn).
然后輸入下面的內(nèi)容:
(lldb) rb '\-\[UIViewController(\(\w+\))?\ '
這條命令在斷點(diǎn)的UIViewController
后面在空格前面提供了一個(gè)包含一個(gè)或多個(gè)字符的括號(hào)選項(xiàng).
正則表達(dá)式斷點(diǎn)讓你用一個(gè)表達(dá)式捕獲一個(gè)寬泛的斷點(diǎn).
你可以用-f
選項(xiàng)來(lái)將斷點(diǎn)指定在特定的文件中.
(lldb) rb . -f DetailViewController.swift
這在你調(diào)試DetailViewController.swift
的時(shí)候很有用.它會(huì)在這個(gè)文件的所有屬性的getters/setters
, blocks/closures
, extensions/ categories
, 和 functions/methods
出打下斷點(diǎn).-f
選項(xiàng)是用來(lái)限定范圍的.
如果你徹底瘋了或者想要折磨一下自己, 你可以想下面這樣簡(jiǎn)單的指定一下范圍:
(lldb) rb .
這會(huì)在所有的地方創(chuàng)建一個(gè)斷點(diǎn)...是的, 是所有地方!這會(huì)在Signals
項(xiàng)目的所有代碼里創(chuàng)建斷點(diǎn), 所有UIKit
的代碼和所有Foundation
的代碼, 所有運(yùn)行循環(huán)的代碼.結(jié)果是, 你需要在調(diào)試器中不停的輸入continue
才能繼續(xù)執(zhí)行.
還有另外一種方式來(lái)指定搜索的范圍.你可以用-s
選項(xiàng)將范圍限制在單一的庫(kù)中:
(lldb) rb . -s Commons
這會(huì)在Commons
庫(kù)中的每一個(gè)地方設(shè)置一個(gè)斷點(diǎn), Commons
是Signals
項(xiàng)目中包含的一個(gè)動(dòng)態(tài)庫(kù).
你也可以用同樣的方法在UIKit
的每一個(gè)函數(shù)里創(chuàng)建斷點(diǎn):
(lldb) rb . -s UIKit
這顯然太瘋狂了.在iOS10.0系統(tǒng)里UIKit
里大概有66,189個(gè)方法.如何在UIKit
里只在你第一次觸發(fā)這個(gè)斷點(diǎn)的時(shí)候停下來(lái)呢?-o
選項(xiàng)提供了一個(gè)解決方案.它創(chuàng)建了one-shot
型的斷點(diǎn). 這種斷點(diǎn)在觸發(fā)一次后就會(huì)自動(dòng)刪除. 因此只會(huì)觸發(fā)一次.
要看這種方法的效果, 可以在LLDB會(huì)話(huà)中輸入下面的命令:
(lldb) breakpoint delete
(lldb) rb . -s UIKit -o
注意:當(dāng)LLDB執(zhí)行這些命令的時(shí)候請(qǐng)耐心等待, 因?yàn)長(zhǎng)LDB創(chuàng)建了大量的斷點(diǎn).并且要確保你使用的是模擬器, 否則的話(huà)你將會(huì)等待非常長(zhǎng)的時(shí)間.
接下來(lái), 讓調(diào)試器繼續(xù)執(zhí)行, 并在tableView中的一個(gè)cell上點(diǎn)擊一下.調(diào)試器就會(huì)在UIKit
的方法第一次調(diào)用的時(shí)候停下來(lái).最后, 繼續(xù)運(yùn)行調(diào)試器, 并且這個(gè)斷點(diǎn)將不會(huì)再次觸發(fā).
修改和刪除斷點(diǎn)
現(xiàn)在你已經(jīng)對(duì)如何創(chuàng)建斷點(diǎn)有了基本的理解, 你可能還會(huì)想知道你如何改變它們. 當(dāng)你想要修改, 刪除或者暫時(shí)停用一個(gè)斷點(diǎn)的時(shí)候該怎么做?當(dāng)你想讓斷點(diǎn)下次觸發(fā)的時(shí)候執(zhí)行一個(gè)特定的命令你又該怎么做呢?
首先, 你需要知道怎樣唯一的標(biāo)記一個(gè)或者一組斷點(diǎn). 在你創(chuàng)建斷點(diǎn)的時(shí)候你也可以用-N
選項(xiàng)命名一個(gè)斷點(diǎn)...用數(shù)字做標(biāo)記可能真的不適合你.
構(gòu)建并運(yùn)行APP來(lái)清空LLDB會(huì)話(huà).接下來(lái), 暫停調(diào)試器并在LLDB會(huì)話(huà)中輸入一下命令:
(lldb) b main
你可能會(huì)看到下面這些輸出:
Breakpoint 1: 20 locations.
這句話(huà)的是意思是說(shuō)你再不同模塊中的與main
匹配的20個(gè)地方創(chuàng)建了斷點(diǎn).
在這種情況下, 這個(gè)斷點(diǎn)的的ID是1, 因?yàn)樗悄銊?chuàng)建的第一個(gè)斷點(diǎn). 要查看這個(gè)斷點(diǎn)的詳細(xì)信息, 你可以用breakpoint list
命令.輸入下面的內(nèi)容:
(lldb) breakpoint list 1
應(yīng)該會(huì)輸出下面類(lèi)似的內(nèi)容:
1: name = 'main', locations = 20, resolved = 20, hit count = 0
1.1: where = Breakpoints`main + 22 at AppDelegate.swift:12, address =
0x00000001057676e6, resolved, hit count = 0
1.2: where = Foundation`-[NSThread main], address = 0x000000010584d182,
resolved, hit count = 0
1.3: where = Foundation`-[NSBlockOperation main], address =
0x000000010585df4a, resolved, hit count = 0
1.4: where = Foundation`-[NSFilesystemItemRemoveOperation main],
address = 0x00000001058990ff, resolved, hit count = 0
1.5: where = Foundation`-[NSFilesystemItemMoveOperation main], address
= 0x0000000105899c23, resolved, hit count = 0
1.6: where = Foundation`-[NSInvocationOperation main], address =
0x00000001058c4fb9, resolved, hit count = 0
1.7: where = Foundation`-[NSDirectoryTraversalOperation main], address
= 0x000000010590a87f, resolved, hit count = 0
1.8: where = Foundation`-[NSOperation main], address =
0x000000010595209c, resolved, hit count = 0
1.9: where = UIKit`-[UIStatusBarServerThread main], address =
0x00000001068b84f0, resolved, hit count = 0
1.10: where = UIKit`-[_UIDocumentActivityItemProvider main], address =
0x000000010691898c, resolved, hit count = 0
1.11: where = UIKit`-[_UIDocumentActivityDownloadOperation main],
address = 0x0000000106975d51, resolved, hit count = 0
1.12: where = UIKit`-[_UIGetAssetThread main], address =
0x000000010698ef4d, resolved, hit count = 0
1.13: where = UIKit`-[UIWebPDFSearchOperation main], address =
0x0000000106ae7c99, resolved, hit count = 0
1.14: where = UIKit`-[UIActivityItemProvider main], address =
0x0000000106c4e525, resolved, hit count = 0
1.15: where = MobileCoreServices`-[LSOpenOperation main], address =
0x000000010879703c, resolved, hit count = 0
1.16: where = ImageIO`main, address = 0x000000010c87535d, resolved, hit
count = 0
1.17: where = AppSupport`-[_CPPowerAssertionThread main], address =
0x000000010ed95f03, resolved, hit count = 0
1.18: where = AppSupport`-[CPDistributedMessagingAsyncOperation main],
address = 0x000000010ed9ba53, resolved, hit count = 0
1.19: where = JavaScriptCore`WTF::RunLoop::main(), address =
0x0000000111c68af0, resolved, hit count = 0
1.20: where = ConstantClasses`main, address = 0x0000000114329cd2,
resolved, hit count = 0
這里顯示了這個(gè)斷點(diǎn)的詳細(xì)內(nèi)容, 包含所有包含main
這個(gè)單詞的位置.
顯示這些信息的簡(jiǎn)介內(nèi)容的方式是輸入下面的內(nèi)容:
(lldb) breakpoint list 1 -b
這會(huì)輸出一些簡(jiǎn)潔的更可視化的信息.如果你有一個(gè)斷點(diǎn)的ID, 并且這個(gè)ID里包含著多個(gè)斷點(diǎn), 這個(gè)簡(jiǎn)明的標(biāo)志是一個(gè)好的解決方案.
如果你想要查看LLDB中所有的斷點(diǎn), 只需要簡(jiǎn)單的輸入下面的內(nèi)容:
(lldb) breakpoint list
你也可以指名多個(gè)斷點(diǎn)的ID和范圍:
(lldb) breakpoint list 1 3
(lldb) breakpoint list 1-3
用breakpoint delete
命令刪除所有的斷點(diǎn)是一個(gè)重量級(jí)的操作.你可以簡(jiǎn)單的使用斷點(diǎn)的ID來(lái)刪除一個(gè)斷點(diǎn)或者斷點(diǎn)的集合:
你可以用指定斷點(diǎn)ID的方式刪除一個(gè)斷點(diǎn):
(lldb) breakpoint delete 1
然而, "main"斷點(diǎn)里面包含了20個(gè)斷點(diǎn).你也可以用下面這總方式刪除一個(gè)斷點(diǎn):
(lldb) breakpoint delete 1.1
這會(huì)刪除1
斷點(diǎn)的第一個(gè)自斷點(diǎn).
我們?yōu)槭裁匆獙W(xué)這些?
在本章節(jié)中你學(xué)到了大量的內(nèi)容. 斷點(diǎn)是一個(gè)很大的話(huà)題并且對(duì)調(diào)試專(zhuān)家來(lái)說(shuō)是一門(mén)主要的藝術(shù)用來(lái)快速的發(fā)現(xiàn)并找到實(shí)物本質(zhì).你也已經(jīng)了解了如何用正則表達(dá)式來(lái)搜索代碼.現(xiàn)在是時(shí)候梳理一下正則表達(dá)式的語(yǔ)法了, 在本書(shū)的后面你將會(huì)用到大量的正則表達(dá)式.
瀏覽 https://docs.python.org/2/library/re.html 來(lái)學(xué)習(xí)正則表達(dá)式.并嘗試找出如何創(chuàng)建不區(qū)分大小寫(xiě)的正則表達(dá)式.
現(xiàn)在你只不過(guò)是出不得了解了一下編譯器是如何生成Objective-C 和 Swift的函數(shù)的.嘗試找出如何在Objective-C的blocks或者Swift的closures里創(chuàng)建斷點(diǎn)的代碼.如果你做到了, 嘗試設(shè)計(jì)出一個(gè)斷點(diǎn)只在Signals
項(xiàng)目里的Commons
framework的Objective-C blocks里停止的斷點(diǎn).這些都是你再未來(lái)創(chuàng)建更復(fù)雜的斷點(diǎn)所需要的技能.