ios调试技巧(4) 通过在调试器中单步调试源代码时观察变量的变化来找出错误的原因。

1,502 阅读6分钟

概述

如果在检查源代码错误的根本原因不是很明显,那么在单步执行代码时观察变量的变化有助于发现错误发生的位置,以便找到可能的原因。

Xcode 调试器提供了几种方法来单步调试代码和检查变量。您可以从断点精确控制代码的执行,根据需要进入和退出调用的函数以确定错误发生的位置。您可以在单步执行代码时监控变量,或暂停执行以更仔细地检查它们。

当您的应用程序在错误发生之前处于没问题的状态,在您认为错误可能即将发生的点处开始您的调查。

在调试器中单步执行代码

当您运行应用程序时,调试器会在遇到的第一个断点处暂停,并且默认情况下会更新显示以显示调试导航器、源代码编辑器、调试栏、变量查看器和控制台。

stepping-through-code-and-inspecting-variables-to-isolate-bugs-1@2x.png

通过选择 Xcode > Preferences > Behaviors > Running,自定义 Xcode 在调试器中运行应用程序时显示的内容。

使用调试栏中的按钮来控制应用程序的执行。

stepping-through-code-and-inspecting-variables-to-isolate-bugs-1@2x.png

-  从暂停位置继续正常执行,直到应用程序在下一个断点处停止,并使用 Continue 按钮。

  • 使用暂停按钮暂停应用程序而不设置断点。应用程序运行时,继续按钮变为暂停按钮。

  • 使用 Step Over 按钮执行相同功能中的下一条指令。也就是该方法的另外一句代码,如果这句代码是调用一个方法,不跳进方法,而是执行当前方法的下一条语句。

  • 使用 Step Into 按钮执行下一条指令。如果下一条指令在另一个方法或函数中,则每次单击 Step Into 按钮时,调试器都会跳转到该函数并继续执行它。

  • 使用 Step Into 后,单击 Step Out 按钮跳过函数的其余部分并返回到调用函数或方法中的下一条指令。

当您逐步浏览您的应用程序时,检查与您的错误相关的变量并注意意外值。

查看代码中的变量值和变量查看器

当您的应用程序在断点处暂停时,将鼠标悬停在源代码中的变量上以查看其当前值。如果变量是无法以文本形式表达的图像或其他类型,请单击右上角的“快速查看”按钮以查看变量的预览。单击打印描述按钮以在控制台中打印对象的描述。

stepping-through-code-and-inspecting-variables-to-isolate-bugs-3@2x.png

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

stepping-through-code-and-inspecting-variables-to-isolate-bugs-4@2x.png

如果适用,每个变量都会显示变量类型、值和指针位置的简要摘要。变量查看器使用 lldb 命令帧变量生成它显示的摘要。如果变量的摘要不可用或仅显示内存指针,请参阅下面的控制台中的评估表达式部分,了解更多检查变量的方法。

单击显示三角形以探索类和结构的实例变量,或其他数据类型的内部结构。选择一个变量并单击“快速查看”按钮以查看变量的预览,单击“打印描述”按钮以在控制台中打印对象的描述。

###请参阅调用堆栈和导航相关代码

当调试器在断点处暂停时,它会在调试导航器中显示当前活动线程和当前调用堆栈,并突出显示断点。调用堆栈表示导致当前断点的函数或方法调用的关系。

stepping-through-code-and-inspecting-variables-to-isolate-bugs-5@2x (1).png

如果您怀疑您的错误在调用函数中,请在调用堆栈中选择一行。调用函数可能会错误地更改实例变量,或者可能在参数中传递了不正确的值。如果源代码在项目中可用,调试器会在变量查看器中显示该点的源代码和相关变量。否则,调试器会显示所选行的汇编代码。此时检查变量是否存在意外值。

选择一个线程以展开或折叠该线程的调用堆栈视图。在调用堆栈中为线程选择一行以查看源代码和变量。

在控制台中执行表达式

要查看比 变量查看器中显示更多的变量或者信息,或者要在调试会话中间改变 变量的值,请使用控制台直接与调试器交互。

使用帧变量或缩短的别名 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不支持点的做法,只支持 [ ]的方式,这点很重要。