~~~~我的生活,我的点点滴滴!!
昨天尝试着使用xcode调试一下程序,发现一个巨大的坑,坑到我实在忍无可忍(虽然不止这一个坑),Objective-C到处可见的@property
但是在xcode里面调试时根本就看不到里面的变量的值,这实在不可容忍,后来冷静下来一想,我就不信苹果的coder都不需要看这个值的。
google一下,后来发现xcode并没有像vs那样的直观可以直接显示出来,要使用LLDB的命令行来查看,虽然没那么直观,但是可能考虑到
coder不用离开键盘的原因才这样设计的,仔细想想其实也不错的,那么下面我们就收集整理一下这些调试技巧与命令吧。
随着Xcode 5的发布,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。它与LLVM编译器一起,带给我们更丰富的流程控制和
数据检测的调试功能。LLDB为Xcode提供了底层调试环境,其中包括内嵌在Xcode IDE中的位于调试区域的控制面板,在这里我们可以直接调
用LLDB命令。如图所示:
我们不需要记住那么多的命令,只需要记住几个重要且xcode没有直接提供的命令就行了。
一、初识LLDB
你可能从未使用过LLDB,那让我们先来热热身。 在调试器中最常用到的命令是p(用于输出基本类型)或者po(用于输出 Objective-C 对象)。
如下,你可以通过输入po 和 view 来输出 view 的信息:
//1
po [self view]
//2
po [note fireDate]
//3
po dic
上面第一个输出会调用view这个object的description,你可能会看到很多像地址的内容:
(UIView *) $1 = 0x0824c800 <UITableView: 0x824c800; frame = (0 20; 768 1004); clipsToBounds = YES; autoresize = W+H;
gestureRecognizers = <NSArray: 0x74c3010>; layer = <CALayer: 0x74c2710>; contentOffset: {0, 0}>
上面第二个输出中 fireDate为note的@property,直接在xcode中无法显示其值的,使用这种方式可以打印出来@property中变量的值
上面第三个输出中 dic是一个NSDictionary字典,也是在xcode中直接看不到里面的内容的,使用po很方便的打印出来所有的值,唯一
可以不爽的是如果NSDictionary里面的内容特别多,那就会占用很多空间(凑合用用吧)。
那么有人会问,这个命令在哪输入了?看下图:
现在知道在什么地方输入了吧。
你可能需要的是 view 下 subview 的数量。由于 subview 的数量是一个 int 类型的值,所以我们使用命令p:
//1
p (int)[[[self view] subviews] count]
最后你看到的输出会是:
//1
(int) $2 = 2
是不是很简单?
细心的朋友可能会发现输出的信息中带有$1、$2的字样。实际上,我们每次查询的结果会保存在一些持续变量中($[0-9]+),
这样你可以在后面的查询中直接使用这些值。比如现在我接下来要重新取回$1的值:
//1
po $1
依然可以看到我们之前使用 po [self view] 的值。
二、常见命令
2.1、help
最简单命令是 help,它会列举出所有的命令。如果你忘记了一个命令是做什么的,或者想知道更多的话,你可以通过 help来了
解更多细节,例如 help print 或者 help thread。如果你甚至忘记了 help 命令是做什么的,你可以试试 help help。
2.2、 print(po、p等的缩写)
LLDB 实际上会作前缀匹配。所以你也可以使用 prin,pri,或者 p。但你不能使用 pr,因为 LLDB 不能消除和 process 的歧
义 (幸运的是 p 并没有歧义)。
你可能还注意到了,结果中有个 $0。实际上你可以使用它来指向这个结果。试试 print $0 + 7,你会看到 106。任何以美元符
开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的,在上面已经介绍过了。
2.3、expr (e、exp等缩写)
可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
如图设置断点,然后运行程序。程序中断后输入下面的命令:
//1
expr a=2
你会看到如下的输出:
//1
(int) $0 = 2
继续运行程序,程序输出的信息是:
//1
实际值:2
很明显可以看出,变量a的值被改变。 除此之外,还可以使用这个命令新声明一个变量对象,如:
//1
expr int $b=2 p $b
下面的命令用于输出新声明对象的值。(注意,对象名前要加$)。
2.4、call
call即是调用的意思。其实上述的po和p也有调用的功能。因此一般只在不需要显示输出,或是方法无返回值时使用call。 和上
面的命令一样,我们依然在viewDidLoad:里面设置断点,然后在程序中断的时候输入下面的命令:
//1
call [self.view setBackgroundColor:[UIColor redColor]]
继续运行程序,看看view的背景颜色是不是变成红色的了!在调试的时候灵活运用call命令可以起到事半功倍的作用。
2.5、bt
2.6、image
NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
NSLog(@"%@",arr[2]);
这段代码有明显的错误,程序运行这段代码后会抛出下面的异常:
现在,我们怀疑出错的地址是0x0000000100004af8(可以根据执行文件名判断,或者最小的栈地址),为了进一步精确定位,
我们可以输入以下的命令:
image lookup --address 0x0000000100004af8
命令执行后返回:
Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53
我们可以看到,出错的位置是RootViewController.m的第53行。
很多时候,LLDB完整的命令是很长的。比如前面所说的image lookup --address这个组合命令。为了方便日常的使用,
提高效率,LLDB命令也提供通过简称的方式调用命令。还是这个命令,我们用简称就可以写为
im loo -a
是不是简单多了。如果你是从gdb时代就开始使用调试器的,你会发现,有些命令如p、call等命令和gdb下是一致的。其实这些命
令是LLDB一些命令的别名,比如p是frame variable的别名,p view实际上是frame variable view。除了系统自建的LLDB别名,
你也可以自定义别名,比如下面这个命令:
command alias ioa image lookup --address %1
是将我前面所介绍过的一个命令image lookup --address添加了一个ioa的别名。然后执行下面的命令:
(lldb) ioa 0x0000000100004af8
Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53
可以看到,我们得到了我们想要的结果,而命令却大大缩短。
三、常见问题
上面我们简单的学习了如何使用LLDB命令。但有时我们在使用这些LLDB命令的时候,依然可能会遇到一些问题,比如下面这个命令。
(lldb) p NSLog(@"%@",[self.view viewWithTag:1001])
error: 'NSLog' has unknown return type; cast the call to its declared return type error: 1 errors parsing expression
如果在使用LLDB命令中发现有 unknown type 的类似错误(多见于id类型,比如NSArray中某个值,或者返回值),那我们就必须显
式声明类型。比如上面这个命令,我们得这么修改。
p (void)NSLog(@"%@",[self.view viewWithTag:1001])
这样就能得到正确的结果了。
大部分时间我只要使用p、po等命令配合xcode提供的UI就够用了。