原文地址:https://www.bignerdranch.com/blog/xcode-breakpoint-wizardry/
每个伟大的开发者应该知道如何使用调试器。Xcode 具有优良的调试器包装 LLDB 的 UI (或者,如果你生活在过去,GDB)、 给所有标准的技巧,如断点、 步进和缩小及围绕你的代码,您可以访问和堆栈帧详细信息。
很多时候在你调试的冒险,基本就够了。但是偶尔,有毛的问题会浮现,要求更多的技巧。例如,想象一下,您在调试时,只发生每隔五分钟,一个定时器,然后触发一些多线程的官样人士,时机至关重要重现一个错误的东西。
我不知道关于您的工作环境,但在我,五分钟就是永恒,和我将被分心闪亮的至少两个对象等待计时器激发。我需要的是想再次抓住我的注意力。
发声的断点 !
当你想你的断点有一个更霸道时,Xcode 可以播放声音,并对齐你回到这一问题的焦点。要配置这,在代码 (或方法定义) 你最喜欢的路线上设置的断点,右键点击它并从菜单中选择"编辑断点..."。
Xcode 会弹出断点编辑器窗口中,有很多很酷的选项。
要使断点通知你一声,你需要添加断点的"行动"。Xcode 提供几种可供选择。
选择"声音"动作,和窗口将更新,使您可以从列表中选择声音被触发时您的断点。
"玻璃屋"是一个不错的选择,因为它是相当响亮而清晰。请记住,您想要在这里提醒。
默认情况下,您的断点将仍然是停留您的应用程序的执行后播放声音。对于时间敏感的情况下或经常发生的事情,这可能是不可取的。
选中"自动评估后继续"框,可使 Xcode 保持谜底后断点评估您的应用程序。惊叹于某个时点,在您的代码中您现在发声警报已被击中了!
也请记住此类断点的存在将介绍简短的打嗝,在您的应用程序的性能,尤其是当在 iOS 设备上运行。时,如果调试器遇到此代码中的点,它会暂停,该线程的执行足够长的时间切换上下文并执行的任何操作附加到该断点。这意味着如果您正在调试非常时间敏感的代码,您的断点操作可能产生不利的影响,对应用程序的能力要一致地重现的东西。一个好的经验法则是保持行动的执行尽可能短。
断点操作的另一个特点是能够在命中断点时执行 shell 命令。我确信你能想到的吨大使用此功能,但本着让事情发声,试试这个:
命令:say
参数:-v, Zarvox, "Your breakpoint %B has been hit %H times"
回溯: 我们怎么在这里?
有时你可能会发现自己在方法或函数,有很多的入口点,在整个代码中调试代码。在这些情况下,您可能想知道应用程序如何到达该点 (或为什么它经常去这点很要命)。
在正常情况下,断点将暂停该应用程序的常规中 Xcode 调试导航器,将显示当前线程的堆栈跟踪,这可能是你需要的一切。再次,虽然,出现时机至关重要的或您不希望应用程序暂停每次命中断点时的情况。
这是在另一个断点操作可以派上用场: 调试器命令。LLDB 提供了许多命令,但在得知你的到达很有用的一个是"bt",将打印出当前线程的堆栈跟踪 (回溯) 到控制台。
结合这"自动继续"与一些特别热或令人困惑的代码路径,然后,您可以看到控制台输出的地方您的代码被击中,穿插其他你可能有,例如从 NSLog 的输出。
2013-10-14 14:28:07.047 MyStuff[62951:a0b] About to call super viewDidLoad
* thread #1: tid = 0x3cccc1, 0x00003076 MyStuff`-[BNRMasterViewController viewDidLoad](self=0x08988fa0, _cmd=0x009bad27) + 102 at BNRMasterViewController.m:35, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
frame #0: 0x00003076 MyStuff`-[BNRMasterViewController viewDidLoad](self=0x08988fa0, _cmd=0x009bad27) + 102 at BNRMasterViewController.m:35
frame #1: 0x003409a8 UIKit`-[UIViewController loadViewIfRequired] + 696
frame #2: 0x00340c44 UIKit`-[UIViewController view] + 35
frame #3: 0x0036b339 UIKit`-[UINavigationController rotatingSnapshotViewForWindow:] + 52
frame #4: 0x00694910 UIKit`-[UIClientRotationContext initWithClient:toOrientation:duration:andWindow:] + 420
frame #5: 0x00270ea2 UIKit`-[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:] + 1495
frame #6: 0x002708c6 UIKit`-[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:] + 82
frame #7: 0x00270798 UIKit`-[UIWindow _setRotatableViewOrientation:updateStatusBar:duration:force:] + 117
frame #8: 0x00270820 UIKit`-[UIWindow _setRotatableViewOrientation:duration:force:] + 67
frame #9: 0x0026f8ba UIKit`__57-[UIWindow _updateToInterfaceOrientation:duration:force:]_block_invoke + 120
...
2013-10-14 14:28:07.048 MyStuff[62951:a0b] Called super viewDidLoad
如果正在调试多线程的应用程序的一部分,您还可以使用命令bt all
要显示的每个线程的堆栈跟踪,当该断点被命中。您也可以打印出来为数有限的堆栈帧通过将数字添加到命令,像bt 10
.
象征性的断点
有时您想要设置一个断点,将命中在某一地方在该应用程序,但你不可能有问题的代码直接访问。想象一下试图调试,你只有一个二进制文件,或东西在 iOS 框架中的第三方库。没有对代码的访问,不能设置断点基于文件名或行号,因为你不知道他们。
这是在那里象征性断点真的可以节省你的培根。使用其中之一,您可以设置基于符号,就像一个方法或函数的名称,无论该名称可能会出现在代码中的断点。
想象一下,你想知道什么时候对任何UIViewController
实例调用viewDidLoad
。您可以设置断点符号为-[UIViewController viewDidLoad]
和它将触发任何方法调用的时间。这将是,例如,任何时候一个子类实现调用[super viewDidLoad]
.
在 Xcode,单击+
在左下角工具栏内断点导航按钮,然后选择"添加符号的断点..."中创建一个具有象征意义的断点
新的断点将添加到列表中,并将弹出一个窗口要求您填写的详细信息。添加您的符号的名称并按回车键以创建新的断点。
如果您启用此断点运行 iOS 应用程序,它将暂停每个方法调用的时间。你可能会发现一些有趣的细节是 iOS 使用断点这样的勇气。
您可以添加一种方法 (选择器) 在特定类中,如上所述,是指的象征性断点或你可以更通用与你的符号,只是提供一个名称,如viewDidLoad
。在这种情况下,Xcode 会做一些令人惊异的事情: 添加断点后,它会查找该名称的应用程序和链接的库中的所有匹配项,使他们那个您刚刚创建的"孩子"断点。
这里是看起来像当您创建断点符号为viewDidLoad
:
令人着迷 !看看所有那些"隐藏"的子类的UIViewController
住在框架内。我们甚至没有爆发类转储 !
观察点
观察点是你可以用来监视更改变量或内存地址的值并发生变化时触发一个停顿在调试器中的工具。他们可以是非常有用的识别程序,你可能不知道正是如何追踪状态的问题。
例如,假设你已经建立一个带有两个按钮和两个标签的用户界面。当您单击其中一个按钮时,标签应该更新,以反映您单击该按钮的次数。
若要跟踪的每个按钮的点击数,您将使用的属性,如button1ClickCount
。为每个按钮,你会有递增 count 属性,并更新相应标签的操作方法。创建这些属性并编写的操作方法。
- (IBAction)button1Clicked:(id)sender {
self.button1ClickCount++;
self.button1ClickCountLabel.text = [self textForButton:1 clickCount:self.button1ClickCount];
}
- (IBAction)button2Clicked:(id)sender {
self.button2ClickCount++;
self.button2ClickCountLabel.text = [self textForButton:2 clickCount:self.button2ClickCount];
}
- (NSString *)textForButton:(NSUInteger)buttonNumber
clickCount:(NSUInteger)clickCount {
return [NSString stringWithFormat:@"Button %d clicked %d times", buttonNumber, clickCount];
}
使行动方法触发响应按钮单击,你尽职尽责地将它们连接起来界面生成器中。然后你运行您的应用程序,并单击每个按钮几次。但是,还有一个问题: 不管您单击了哪个按钮,你看到只有第一个按钮计数的更新 !
你怎么可以调试这?在这平凡的例子,当然你可以在每个操作方法中设置断点并确保他们都得到适当的触发。但是想象一下,递增计数的代码是真的一些复杂的逻辑,生活在一个更加复杂的 API 与另一个类。当你不知道何时何地正在改变一个变量时,你可以设置监视。
意义在这里设置为按钮 1 点击计数的值的监视。可以对变量的值设置观察点,但button1ClickCount
成立了作为一个属性。你怎么能看它?你可以依靠备份该属性的自动合成的实例变量: _button1ClickCount
.
若要设置监视,您需要有您想要观看的范围变量的堆栈帧内调试器中暂停。为便于演示,将断点放入button1Clicked:
操作方法和通过单击第一个按钮来触发它。Xcode 将会暂停该应用程序,然后您可以在两种方式之一来创建监视: 使用 Xcode GUI 或 LLDB 控制台。
若要使用 GUI,导航到变量视图、 揭露你想看,的变量和 (control 键单击),右击它的名称。在出现的菜单中,选择"观看 _button1ClickCount"。
如果您愿意,可以代替键入在 LLDB 控制台中设置监视:
(lldb) watch set variable _button1ClickCount
Watchpoint created: Watchpoint 1: addr = 0x0b9a1ca4 size = 4 state = enabled type = w
declare @ 'MyStuff/MyStuff/BNRDetailViewController.m:52'
watchpoint spec = '_button1ClickCount'
new value: 10
默认情况下,检查点将手表为您指定的变量上"写"的发生。这意味着当设置了该变量的值,检查点将会打。
设置此监视,您可以单击任一按钮和看到它被触发:
您还可以看到 LLDB 有用地吐出旧值和新值的控制台中的变量:
Watchpoint 1 hit:
old value: 10
new value: 11
在下面的例子,这一问题的根本原因是按钮 2 的"触摸起来里面"行动不正确的有线button1Clicked:
方法。它应该非常明显,然而,观察点可以帮助你在那里变化正在发生,你不太会对为什么的句柄的情况下。
你可以了解更多关于LLDB 教程中,通过在 LLDB 控制台中键入help watch
的观察点。需要注意的一个重要细节: 观察点不会保存您的程序,所以如果你需要重复设置监视你应该保存在另一个文件中设置它的命令并将其粘贴到调试器控制台在适当的时候执行之间。
您还可以使用组合在这里讨论,以帮助技术: 命中断点设置在程序执行的早期哪里您想要观看的变量是在范围内,与动作该断点 LLDB 命令来设置监视。
认识到 (休息) 问题
Xcode 与 LLDB,大量的调试选项是在您的处置。知道何时以及如何使用这些功能强大的工具可以在代码中可以真正利用困难虫子咬。
你有任何其他极棒的调试器提示和技巧吗?随意张贴他们在评论中 !