在一款完整iOS移動應用的開發中,代碼的調試和編寫占着同等重要的地位。Xcode預設使用LLDB作為代碼調試器,LLDB功能豐富且強大,恰當的使用它,可以幫助開發者事半功倍的完成代碼調試的工作。
1.expression代碼執行指令
關于LLDB調試器,最常用的指令應該是p與po了,開發者常用這兩個指令來進行對象的列印操作,p會列印出對象位址和類型,po則會額外列印出對象的值得内容,實際上,這兩個指令都是expression相關指令的簡寫。expression指令也并非簡單的列印指令,實際上它是一個執行代碼指令,執行後将傳回值進行列印,這個指令有一個十分強大的特點,它可以真實改變程式運作中變量的值。例如在如下代碼中的int c = a+b 一行添加一個斷點,運作工程。
int a = 0;
int b = 1;
int c = a+b;
NSLog(@"%d",c);
如果開發者不進行任何認為操作,此時列印出的值應該是1,為了測試,可以在調試區輸入如下指令:
(lldb) expression a=1
此後跳過斷點繼續運作程式,可以看到列印的結果如下,c變成2。
(int) $0 = 1
2016-04-24 11:39:40.213 BreakPointTest[1010:79065] 2
通過上面的示範,我們發現使用LLDB調試代碼十分友善的一個特點,當我們知道程式某個地方可能會出現問題,為了找到解決方法,不使用LLDB時我們可能需要在代碼中添加大量的列印函數,并且多次嘗試修改源代碼才能解決問題,如果使用LLDB的expression指令,我們不僅不需要添加額外的列印代碼,也不需要直接修改源代碼,在調試區進行多次調試,直到找到正确的修改方法後再對源代碼修改一次即可。
2.frame代碼堆棧塊資訊相關指令
當Xcode進入斷點調試或者遇到異常程式崩潰時,在Xcode左側的導航區都會将程式運作中的相關堆棧塊資訊列舉出來,例如使用如下測試代碼,在text方法中的int c = a+b 一行添加一個斷點。
#import "ViewController.h"
@interface ViewController ()
{
int ab;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
ab = 1;
[self test];
-(void)test{
當程式運作到斷點處斷開時,Xcode左側的堆棧塊如下圖所示:
從圖中可以看出,程式目前處于激活狀态的線程有5個,程式目前斷線上程1中的test方法堆棧塊中,使用frame info指令可以列印目前堆棧塊的資訊,示例如下:
(lldb) frame info
frame #0: 0x0000000102497905 BreakPointTest`-[ViewController test](self=0x00007fcd5b413320, _cmd="test") + 37 at ViewController.m:39
在列印的資訊中,會有所在的檔案名稱和函數名稱及堆棧塊标号和記憶體位址。
在實際代碼調試過程中,程式運作的回溯是一個重要的方法,例如上面的代碼例子,雖然現在斷點斷在test方法中,開發者可能需要在viewDidLoad方法中進行相關調試,例如上面viewDidLoad方法中有一個變量ab,如果想檢視ab變量的值,我們就需要将目前選中調試的堆棧塊選擇為viewDidLoad方法所在的堆棧塊,從Xcode左側導航區可以看到,viewDidLoad方法堆棧塊的标号為1,執行如下LLDB指令即可切換:
(lldb) frame select 1
frame #1: 0x00000001024978cb BreakPointTest`-[ViewController viewDidLoad](self=0x00007fcd5b413320, _cmd="viewDidLoad") + 91 at ViewController.m:31
28 int a = 0;
29 int b = 1;
30 int c = a+b;
-> 31 NSLog(@"%d",c);
32 }
33 @end
從列印資訊可以看到,現在選中的調試堆棧塊已經切換到viewDidLoad方法,再使用expression指令時就可以操作這個方法中的相關變量了。
在使用LLDB工具前,遇到這樣的情況,我往往會采用打多個斷點,一步步追溯代碼的運作過程并檢查過程中變量的值是否正确,調試起來并不十分友善,如果不小心錯過了某個斷點,又要重新開始,通過選擇調試的frame堆棧塊可以十分友善的解決這個問題。
與frame相關的還有一個指令十分有用,下面的指令可以列印出目前堆棧塊中所有對象的内容:
(lldb) frame variable
(ViewController *) self = 0x00007fcd5b413320
(SEL) _cmd = "test"
(int) a = 0
(int) b = 1
(int) c = 0
variable後面也可以添加參數名來列印特定對象的内容:
(lldb) frame variable a
c指令繼續運作線程和process continue效果一樣。
call指令運作一個表達式,和 expression 效果一樣。
detach指令結束目前調試的線程。
di指令反彙編目前函數與disassemble相同。
exit指令退出lldb調試器。
finish指令完成目前堆棧塊的調試,程式會繼續運作。
n指令進行單步調試,與next作用一樣。
p指令與expression作用一樣。
print指令用于變量的列印。
r指令重新運作應用程式。
quit指令結束調試。
bugreport指令用于建立堆棧資訊報告。
command history指令用于列印LLDB調試指令記錄。
help指令用于查詢LLDB相關調試指令的用法。
apropo指令用于查詢某些包含某些關鍵字的指令。
version指令用于查詢LLDB調試器的版本,如下:
(lldb) version
lldb-350.0.21.3
image list指令用于列印工程中所有用到的庫檔案。
image相關指令還有一個十分有用的指令,image lookup --address可以查詢某個記憶體位址的内容,如下:
(lldb) image lookup --address 0x000000010373e885
Address: CoreFoundation[0x00000000000f4885] (CoreFoundation.__TEXT.__text + 996309)
Summary: CoreFoundation`-[__NSArray0 objectAtIndex:] + 101
image lookup --type用于查詢某種類型中包含的屬性,如下:
(lldb) image lookup --type UILabel
Best match found in /Users/vip/Library/Developer/Xcode/DerivedData/BreakPointTest-cearqrjqbntqcnfgiqzpxhyadewi/Build/Products/Debug-iphonesimulator/BreakPointTest.app/BreakPointTest:
id = {0x000082c1}, name = "UILabel", byte-size = 8, decl = UILabel.h:18, compiler_type = "@interface UILabel : UIView
@property ( getter = text,setter = setText:,readwrite,copy,nonatomic ) NSString * text;
@property ( getter = font,setter = setFont:,readwrite,nonatomic ) UIFont * font;
@property ( getter = textColor,setter = setTextColor:,readwrite,nonatomic ) UIColor * textColor;
@property ( getter = shadowColor,setter = setShadowColor:,readwrite,nonatomic ) UIColor * shadowColor;
@property ( getter = shadowOffset,setter = setShadowOffset:,assign,readwrite,nonatomic ) CGSize shadowOffset;
@property ( getter = textAlignment,setter = setTextAlignment:,assign,readwrite,nonatomic ) NSTextAlignment textAlignment;
@property ( getter = lineBreakMode,setter = setLineBreakMode:,assign,readwrite,nonatomic ) NSLineBreakMode lineBreakMode;
@property ( getter = attributedText,setter = setAttributedText:,readwrite,copy,nonatomic ) NSAttributedString * attributedText;
@property ( getter = highlightedTextColor,setter = setHighlightedTextColor:,readwrite,nonatomic ) UIColor * highlightedTextColor;
@property ( getter = isHighlighted,setter = setHighlighted:,assign,readwrite,nonatomic ) BOOL highlighted;
@property ( getter = isUserInteractionEnabled,setter = setUserInteractionEnabled:,assign,readwrite,nonatomic ) BOOL userInteractionEnabled;
@property ( getter = isEnabled,setter = setEnabled:,assign,readwrite,nonatomic ) BOOL enabled;
@property ( getter = numberOfLines,setter = setNumberOfLines:,assign,readwrite,nonatomic ) NSInteger numberOfLines;
@property ( getter = adjustsFontSizeToFitWidth,setter = setAdjustsFontSizeToFitWidth:,assign,readwrite,nonatomic ) BOOL adjustsFontSizeToFitWidth;
@property ( getter = baselineAdjustment,setter = setBaselineAdjustment:,assign,readwrite,nonatomic ) UIBaselineAdjustment baselineAdjustment;
@property ( getter = minimumScaleFactor,setter = setMinimumScaleFactor:,assign,readwrite,nonatomic ) CGFloat minimumScaleFactor;
@property ( getter = allowsDefaultTighteningForTruncation,setter = setAllowsDefaultTighteningForTruncation:,assign,readwrite,nonatomic ) BOOL allowsDefaultTighteningForTruncation;
@property ( getter = preferredMaxLayoutWidth,setter = setPreferredMaxLayoutWidth:,assign,readwrite,nonatomic ) CGFloat preferredMaxLayoutWidth;
@property ( getter = minimumFontSize,setter = setMinimumFontSize:,assign,readwrite,nonatomic ) CGFloat minimumFontSize;
@property ( getter = adjustsLetterSpacingToFitWidth,setter = setAdjustsLetterSpacingToFitWidth:,assign,readwrite,nonatomic ) BOOL adjustsLetterSpacingToFitWidth;
@end"
x指令可以讀取某段記憶體的二進制資料:
(lldb) x 0x000000010373e885
0x10373e885: 66 66 2e 0f 1f 84 00 00 00 00 00 55 48 89 e5 48 ff.........UH..H
0x10373e895: 8d 3d 6d f2 28 00 e8 c0 d9 f0 ff 48 89 05 c1 58 .=m.(......H...X
LLDB的用法和技巧還有很多,它可以大大提高我們調試代碼的效率,有疏漏和錯誤之處,還望與志同道合的朋友共同學習進步。