概述
如果在检查源代码错误的根本原因不是很明显,那么在单步执行代码时观察变量的变化有助于发现错误发生的位置,以便找到可能的原因。
Xcode 调试器提供了几种方法来单步调试代码和检查变量。您可以从断点精确控制代码的执行,根据需要进入和退出调用的函数以确定错误发生的位置。您可以在单步执行代码时监控变量,或暂停执行以更仔细地检查它们。
当您的应用程序在错误发生之前处于没问题的状态,在您认为错误可能即将发生的点处开始您的调查。
在调试器中单步执行代码
当您运行应用程序时,调试器会在遇到的第一个断点处暂停,并且默认情况下会更新显示以显示调试导航器、源代码编辑器、调试栏、变量查看器和控制台。

通过选择 Xcode > Preferences > Behaviors > Running,自定义 Xcode 在调试器中运行应用程序时显示的内容。
使用调试栏中的按钮来控制应用程序的执行。

- 从暂停位置继续正常执行,直到应用程序在下一个断点处停止,并使用 Continue 按钮。
-
使用暂停按钮暂停应用程序而不设置断点。应用程序运行时,继续按钮变为暂停按钮。
-
使用 Step Over 按钮执行相同功能中的下一条指令。也就是该方法的另外一句代码,如果这句代码是调用一个方法,不跳进方法,而是执行当前方法的下一条语句。
-
使用 Step Into 按钮执行下一条指令。如果下一条指令在另一个方法或函数中,则每次单击 Step Into 按钮时,调试器都会跳转到该函数并继续执行它。
-
使用 Step Into 后,单击 Step Out 按钮跳过函数的其余部分并返回到调用函数或方法中的下一条指令。
当您逐步浏览您的应用程序时,检查与您的错误相关的变量并注意意外值。
查看代码中的变量值和变量查看器
当您的应用程序在断点处暂停时,将鼠标悬停在源代码中的变量上以查看其当前值。如果变量是无法以文本形式表达的图像或其他类型,请单击右上角的“快速查看”按钮以查看变量的预览。单击打印描述按钮以在控制台中打印对象的描述。

变量查看器列出了当前执行上下文中可用的变量。从查看器左下方的选择器中选择要查看的变量范围:auto自动变量、局部或所有变量、寄存器、全局变量和静态变量。使用过滤器字段查找与模式匹配的变量。

如果适用,每个变量都会显示变量类型、值和指针位置的简要摘要。变量查看器使用 lldb 命令帧变量生成它显示的摘要。如果变量的摘要不可用或仅显示内存指针,请参阅下面的控制台中的评估表达式部分,了解更多检查变量的方法。
单击显示三角形以探索类和结构的实例变量,或其他数据类型的内部结构。选择一个变量并单击“快速查看”按钮以查看变量的预览,单击“打印描述”按钮以在控制台中打印对象的描述。
###请参阅调用堆栈和导航相关代码
当调试器在断点处暂停时,它会在调试导航器中显示当前活动线程和当前调用堆栈,并突出显示断点。调用堆栈表示导致当前断点的函数或方法调用的关系。

如果您怀疑您的错误在调用函数中,请在调用堆栈中选择一行。调用函数可能会错误地更改实例变量,或者可能在参数中传递了不正确的值。如果源代码在项目中可用,调试器会在变量查看器中显示该点的源代码和相关变量。否则,调试器会显示所选行的汇编代码。此时检查变量是否存在意外值。
选择一个线程以展开或折叠该线程的调用堆栈视图。在调用堆栈中为线程选择一行以查看源代码和变量。
在控制台中执行表达式
要查看比 变量查看器中显示更多的变量或者信息,或者要在调试会话中间改变 变量的值,请使用控制台直接与调试器交互。
使用帧变量或缩短的别名 v 打印当前堆栈帧中的变量值。
(lldb) v self.fruitList.title
(String) self.fruitList.title = "Healthy Fruit”
(lldb) v self.listData[0]
(String) [0] = “Banana"
帧变量命令仅返回当前内存中的内容并且不计算表达式,因此如果您尝试打印更多内容,它会返回错误。例如,它不会打印函数或方法调用、@Published 变量或计算变量。
(lldb) v fruitList.fruit(at: indexPath)
error: no variable named 'fruitList' found in this frame
error: no variable named 'indexPath)' found in this frame
(lldb) v self.fruitList.calculatedFruitCount
error: "calculatedFruitCount" is not a member of "(Debugger_Demo.FruitList) self.fruitList”
使用表达式命令或别名 expr 或 p 计算表达式并在控制台中打印结果。
(lldb) p self.fruitList.calculatedFruitCount
(Int) $R18 = 9
(lldb) p fruitList.fruit(at: indexPath)
(Debugger_Demo.FruitItem) $R20 = 0x00006000013dcc90 (fruitName = "Strawberry", fruitDescription = "Small red berry with seeds on the outside.”)
(lldb) expr fruit.fruitName
(String) $R14 = "Strawberry"
(lldb) p fruit.fruitName == "Peach"
(Bool) $R16 = false
p 命令 先编译代码然后评估表达式,因此它处理函数调用和计算变量。使用 p 提供的引用作为其他表达式的一部分。
(lldb) p fruit.fruitName
(String) $R2 = "Banana"
(lldb) p fruit.fruitName
(String) $R6 = "Strawberry"
(lldb) p $R2 + ", " + $R6
(String) $R8 = "Banana, Strawberry"
对于某些类,使用 p 可能只显示一个内存指针位置,或者可能显示该类所有属性的完全展开视图,这可能是很多不必要的信息。在这些情况下,请使用 po,即表达式 — 对象描述的别名。此版本还编译代码以评估表达式,但它会打印结果的对象描述,您可以为您的对象自定义该描述。
注意:
p 和po 的区别是,p可能只会打印对象的地址,然后po可以打印属性和其他详细的描述。
(lldb) p self.testLabel
(LWZTestLabel *) $4 = 0x00000001074041a0
(lldb) po self.testLabel
<LWZTestLabel: 0x1074041a0; baseClass = UILabel; frame = (100 100; 100 50); text = '点击屏幕后的文字'; hidden = YES; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x28354c190>>
(lldb) p self.tapNum
(NSInteger) $6 = 1
(lldb) po self.tapNum
1
通过添加调试描述自定义调试器为您的对象显示的内容。在 Swift 中,为您的对象实现 CustomDebugStringProtocol。对于扩展 NSObject 的 Objective-C 对象,覆盖 debugDescription。
po 打印的输出的内容可以通过重写description方法更改,swift也有方法更改。
还可以通过po 或者p去更改内存变量的值。
当您打印使用协议声明的项目时, p 和 po 会打印错误,因为它们不执行迭代动态类型解析。当 p 或 po 打印错误时,使用 v 打印变量。
(lldb) po fruitItem.fruitName
error: <EXPR>:3:11: error: value of type 'FruitDisplayProtocol' has no member 'fruitName'
fruitItem.fruitName
~~~~~~~~~ ^~~~~~~~~
(lldb) v fruitItem.fruitName
(String) fruitItem.fruitName = "Apple
重点:
使用 Objective-C 时,lldb 不支持用于消息发送的点表示法。使用括号表示法并将结果转换为 CGRect:
特别注意的是lldb不支持点的做法,只支持 [ ]的方式,这点很重要。