[译] Xcode 和 LLDB 高级调试教程:第 3 部分

在这三部分教程的第一部分和第二部分中,咱们已经介绍了如何利用 Xcode 断点来控制一个存在的属性值,而且经过表达式语句注入新的代码行。咱们还探索了观察点这种特殊类型的断点。前端

我开发了一个特地带有几个错误的演示项目,详细说明了如何使用不一样类型的断点配合 LLDB 来修复项目/应用程序中的错误。android

若是你尚未看过本教程的 第一部分第二部分,最好先看过它们再继续阅读本文。ios

最后,本教程的指导原则是:git

第一次运行应用程序后,你没必要中止编译器或从新运行应用程序,你将在运行时修复这些错误。github

符号断点 🔶

到目前为止咱们还要作什么?express

  1. 导航栏左侧指示用户加载次数的标签没有更新。

这里有一些步骤能够复现这最后一个须要处理的错误:json

✦ 滚动到表视图的顶部,而后下拉刷新。后端

✦ 滚动到表视图的底部去加载更多文章。[7 次 😉]xcode

✦ 在每次成功获取到新的文章以后,左侧标签并无被更新。bash

须要指出的是整型属性 pageNumber 回答了这个问题,用户已经加载了文章多少次?(换句话说,导航栏左侧的标签应该被 pageNumber 属性的值更新)。咱们能够确信的是,在以前的修复中 pageNumber 属性的值已经能够更新了。所以如今的问题在于没有将它的值设置给导航栏左侧的标签。

在这种状况下,符号断点会介入。想象一下,符号断点就像调试器在玩寻宝,而你会提供一些寻宝的线索。对你来讲,这会发生在更新导航栏左侧标签的代码片断中。

让我告诉你接下来怎么作。

展开 Breakpoint navigator,接着点击左下角 + 按钮,选择 Symbolic Breakpoint。

在 Symbol 栏添加以下符号

[UILabel setText:]
复制代码

不要 勾选 “Automatically continue after evaluating actions” 选项框。

咱们所作的只是告诉调试器,当任何一个 UILabel 的 setText 方法被调用的时候,它就会暂停。注意这里在建立了一个符号断点以后,一个子断点会被添加。

这是来自调试器的反馈,它可以解析这个建立的符号断点到 UIKitCore 框架的特定位置。在其余状况下,调试器也许会解析到多个位置。

如今一切就绪,下拉以刷新表视图的文章。当你释放以后,调试器就会暂停,接着你会看到以下图的东西:

在这时你会看到一些 UIKitCore 框架的汇编代码,在左侧的是致使调试器暂停的堆栈信息。下一步咱们要作的是,检查在调试器暂停的位置传入 Objective-C 消息的参数。在 lldb 控制台输入下面的命令:

po $arg1
复制代码

这会指出持有第一个参数的寄存器。咱们能清楚的看到接受这个 Objective-C 消息的是一个 UILabel 实例。这个 UILabel 实例有一个文本值指向一个文章的标签。这不是咱们所感兴趣的,不过让咱们继续寄存器检查。

在 lldb 控制台,输入以下指令:

po $arg2
复制代码

$arg2 始终指向 Objective-C 消息的选择器。在某些状况下,lldb 并不彻底的清楚参数的类型,所以咱们须要作一些类型转换的工做。

在 lldb 控制台,输入以下指令:

po (SEL)$arg2
复制代码

如今咱们很清楚的看到了当前 Objective-C 消息的选择器。

在 lldb 控制台,输入以下指令:

po $arg3
复制代码

$arg3 始终指向传入方法的第一个参数。在咱们的情形下,传入 setText 方法的参数一个字符串。

继续执行程序。调试器会再次暂停。重复前面的步骤,最终,你发现这个 Objective-C 消息属于在表视图里的另外一个文章标签。直到咱们找到咱们感兴趣的那个 UILabel 实例前,一遍又一遍的作这个事情确实很无趣。确定有更好的方式。

你可以作的一件事就是为符号断点设置条件,以便在成功或知足条件时暂停调试器。它可以检查布尔值或者等待条件达成诸如此类。

然而,咱们采用一种不一样的方法。

One Shot!

将咱们建立的符号断点设置为不可用。

讲道理,导航栏左侧的标签指示了用户加载文章的次数,它会在 HTTP GET 请求成功完成以后被更新。找到有 pragma mark Networking 的部分。在 loadPosts 成功完成的回调里放置一个断点。这个断点应该放在以下的位置:

Objective-C

[self.tableView reloadData];
复制代码

Swift

self.tableView.reloadData()
复制代码

这会确保符号断点只有在表视图从新加载数据以后才会被触发,全部相等的标签都已经被更新。

不要 勾选 “Automatically continue after evaluating actions” 选项框。添加以下的调试器命令动做:

breakpoint set --one-shot true -name '-[UILabel setText:]'
复制代码

🤨🧐🤔

让咱们拆解这个命令:

  1. breakpoint set --one-shot true 会建立一个 “one-short” 断点。one-shot 断点是一种建立以后,首次触发就会自动删除的断点。

  2. -name ‘- [UILabel setText:]’ 给建立的 one-shot 断点设置了一个符号名。这和你上一节所作的很是类似。

让我总结一下这一部分。你所作的有:

  1. 在发起 GET 请求成功完成的回调里添加断点(A)。

  2. 添加调试器命令动做去 建立 符号断点(B)和上一节建立的很类似。这个符号是 UILabel setText 方法。

  3. 将你建立的符号断点(B)设置为一个 one-shot 断点。one-shot 断点在触发后会被自动删除,这意味着符号断点只会暂停调试器一次。

  4. 断点(A)被放置在表视图加载完成以后,所以建立的符号断点(B)不会因任何和表视图相关联的标签而暂停调试器。

如今下拉表视图去刷新。咱们会获得以下内容:

Objective-C

Swift

因为设置了 one-shot 断点调试器停在了断点(A)的位置。

继续执行程序。

你会返回到 UIKitCore 框架的汇编代码。

让咱们检查一下符号断点参数的 Objective-C 消息。

在 lldb 控制台,输入以下的指令:

po $arg1
复制代码

哇哦,看起来你找到了宝藏! 🥇🏆🎉

是时候把咱们的目光转移到堆栈跟踪信息了。走到点 1 的位置。

Objective-C

Swift

它会引导你到这块更新 pageNumberLabel 文本的代码。这块代码很明显为文本始终设置了整型值为 0 而不是 pageNumber 属性的格式字符串。让咱们在实际修改代码前先测试一下。

你如今已是行家了 🧢

在已标记的代码分隔线下添加一个断点。添加以下的调试器命令动做:

Objective-C

expression self.pageNumberLabel.text = [NSString stringWithFormat:@"Page %tu", self.pageNumber]
复制代码

Swift

expression pageNumberLabel.text = String(format: "Page %tu", pageNumber)
复制代码

移除或者停用断点(A),相应地,断点(B)也会被停用。

如今下拉刷新和加载更多文章。左侧导航栏标签将会被更新。 🎉

任务完成! 💪 💪

如今你能够中止编译器而且在代码中去修复咱们讨论的这些问题。

总结

在这个教程里,你学会了

  1. 如何使用断点配合调试器动做表达式去控制存在的属性值。

  2. 如何使用断点配合调试器动做表达式注入代码。

  3. 如何为某个属性设置观察点监视属性值的变化。

  4. 如何基于定义的符号使用符号断点暂停调试器。

  5. 如何使用 one-shot 断点。

  6. 如何使用 one-shot 断点配合符号断点。

调试愉快! 😊

第三方工具

为了本教程的方便,我使用了下面的第三方工具。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索