Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

73 diagnosing and resolving bugs in your running app #139

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# 诊断和解决运行中应用的错误

检查应用以隔离错误、定位崩溃、识别过多的系统资源使用、可视化内存错误并调查其外观问题。

## 概述

单元测试能确定代码是否能提供符合预期的结果,但不能解释不符合预期的原因。要诊断错误,请连接调试器,重现错误,然后在应用运行时使用断点检查代码中关键点的变量,从而找出错误的根本原因。如果在配置方案的运行操作的“信息(Info)”标签下勾选了“调试可执行文件(Debug executable)”复选框,应用在使用该配置方案时就会自动附加调试器。要将调试器附加到已运行的进程,请选择“调试(Debug)” → “附加到进程(Attach to Process)”,然后从列表中选择应用进程。按照同样的流程诊断并解决代码中的错误、崩溃、内存泄漏和布局问题。

### 暂停应用以检查变量并隔离错误

要修复错误,首先需要了解导致错误的原因。要缩小错误原因的范围,需要制定一套可靠的重现步骤:

1. 确定源代码中错误发生的位置。
2. 源代码中,在你认为错误出现的位置之前设置断点,暂停应用。
3. 查看变量,确认它们是否拥有你所期望的值。如果没有,请从步骤1重新开始。
4. 逐步检查代码,观察变量的变化。注意变量出现意外值的位置。
5. 分析代码,确定修复方法。

确定了错误的尝试性修复方案后,修改代码并重新测试来试着重现错误,从而确认诊断。如果更改后问题得到了解决,则说明你已经解决了错误。如果更改后问题仍未解决,则应重新考虑错误可能发生的位置,并重复上述步骤以隔离和修复错误。

有关设置断点和检查变量的更多信息,请参阅[设置断点以暂停正在运行的应用](https://developer.apple.com/documentation/Xcode/setting-breakpoints-to-pause-your-running-app),以及[逐步检查代码和变量以隔离错误](https://developer.apple.com/documentation/Xcode/stepping-through-code-and-inspecting-variables-to-isolate-bugs)。

### 查找崩溃、异常和运行时问题

当你的应用出现崩溃、异常或运行时问题时,由于崩溃的堆栈跟踪并不总是指向导致崩溃的代码行,因此要准确定位导致问题的代码可能很有难度。请使用下面的规则识别问题的特征,然后设置正确的断点类型:

- 停止在 `main` 或突出显示 `AppDelegate` 的崩溃通常是 Objective-C 异常。
- 由运行时问题导致的崩溃也会在 `main` 或突出显示 `AppDelegate` 时停止,并可能出现类似以下的消息: “Thread 8: EXC_BAD_INSTRUCTION (code=…)”。
- 在未捕获或未处理的 Swift 错误处停止的崩溃会显示致命错误消息并指向 Swift 错误。

根据问题特征在代码中的特定位置添加断点,然后当应用在断点处停止时,检查代码执行的状态。有关设置断点和识别崩溃的更多信息,请参阅[设置断点以暂停正在运行的应用](https://developer.apple.com/documentation/Xcode/setting-breakpoints-to-pause-your-running-app)和[识别常见崩溃的原因](https://developer.apple.com/documentation/xcode/identifying-the-cause-of-common-crashes)。

> 注意
> 当你的 Swift 代码使用来自使用 Objective-C 的模块代码时,可能会接收到 Objective-C 异常。

### 在不暂停的情况下检查变量和执行顺序

在开发代码时,记录操作和变量值很有帮助,这样你就能了解代码的运行方式以及变量在应用中不同点的值。当你开发*并行代码*(即在多个队列或线程中同时执行的代码)时尤其如此,因为错误可能是间歇性的,很难重现。通常情况下,你在正常执行时会重现错误,但在调试器中却不会,因为正常执行和调试之间的时序不同。调试器提供了在不暂停和干扰并行代码时序的情况下检查变量的工具。

开发人员通常会添加 `print` 或 `NSLog` 语句来查看变量值。这种方法虽然有效,但会增加额外的代码,且这些代码在开发完成后并无用处,还会给应用留下一个嘈杂的控制台,使诊断后续错误变得更加困难。相反,我们可以使用断点操作来了解应用中事件的发生时间,并在不暂停的情况下检查变量值。

想要在对时序影响最小的情况下确认代码是否执行,请使用断点操作播放声音并继续执行。如果调试器在运行应用时到达了断点,它就会播放声音并确认执行。

要在不暂停的情况下将变量值记录到控制台,可通过“调试器命令”操作添加一个断点,使用 `po` 将对象的评估结果打印出来,或使用 `v` 将变量值打印到控制台。为断点选择“评估操作后自动继续”选项以防止暂停。

<img src="https://docs-assets.developer.apple.com/published/f6ac0431871e13b8fe9ede7c698b989b/[email protected]">

要将自定义文本记录到控制台并为变量值添加上下文,可添加带“日志信息”操作的断点。指定自定义文本,并包含表达式、断点名称或断点命中计数,以提供更多信息。

> 注意
> 由于 `po` 会动态编译代码以评估表达式,因此评估变量并将其记录到控制台需要更多时间。为减少时序问题,可使用 `v` 来记录变量值。

使用其他断点操作可执行 AppleScript 或 shell 脚本,或捕获 GPU 帧。

有关检查变量的更多信息,请参阅[设置断点以暂停正在运行的应用](https://developer.apple.com/documentation/Xcode/setting-breakpoints-to-pause-your-running-app),以及[逐步检查代码和变量以隔离错误](https://developer.apple.com/documentation/Xcode/stepping-through-code-and-inspecting-variables-to-isolate-bugs)。

### 识别潜在的 CPU 和内存过度使用

在开发和测试过程中,CPU 和内存的过度使用是一个经常容易被忽视的问题。Xcode 的调试器在调试导航器中提供了仪表盘,以帮助调查潜在问题。在测试应用时监控仪表盘,以发现异常使用情况。点击仪表盘可查看更详细的信息。

<p align="center">
<img src="https://docs-assets.developer.apple.com/published/0bd53b098c7e8d44c54160d4f318e68c/[email protected]" width="50%">
</p>

CPU 测量值显示应用在一段时间内处理指令所需的 CPU 占用率。当应用正在绘制用户界面、处理从网络获取的数据或执行计算时,CPU 使用率会在短时间内上升到相当高的水平,这是正常现象。当这些任务完成后,应用处于闲置状态,等待用户执行操作时,CPU 使用率应为零或非常低。如果出现以下情况,请进行额外分析:

- 当应用看似闲置时,CPU 使用率持续高于零。
- 使用率多次超过100%。
- 非常高且用户界面出现卡顿。

有关提高性能的更多信息,请参阅[提高应用的性能](https://developer.apple.com/documentation/Xcode/improving-your-app-s-performance)。

内存量表显示应用在一段时间内使用的内存量。第一次启动应用时,内存使用量很小,不到 10 MB,随着用户浏览用户界面,内存使用量会逐渐增加。如果你从网络中获取、处理和存储数据,或执行复杂的计算,它也会增加。当处理完成后,它就会降低。在你浏览应用时,请注意观察仪表盘,并注意内存使用量何时上升,何时下降。当你显示模态视图或向将视图添加到导航控制器时,内存使用量会增加;而当你取消或从这些视图返回时,内存使用量会减少。如果内存使用量持续增加而不减少,请调查是否存在内存泄漏或内存弃用的情况。有关减少内存使用和解决内存泄漏问题的更多信息,请参阅下面的章节[可视化并诊断内存使用量的增加](https://developer.apple.com/documentation/xcode/diagnosing-and-resolving-bugs-in-your-running-app#Visualize-and-diagnose-increasing-memory-usage)和[减少应用的内存使用量](https://developer.apple.com/documentation/xcode/reducing-your-app-s-memory-use)。

### 检测高磁盘访问和网络使用

请注意频繁访问磁盘和网络资源导致的问题。你也可以使用 Xcode 调试器中的仪表来监控这些资源。磁盘 I/O 仪表显示应用在一段时间内从磁盘读取和写入的数据量。该仪表显示你是否

- 在应用中存储用户生成的数据。
- 在用户偏好设置中存储数据。
- 从网络获取数据并存储。
- 从应用捆绑包或应用目录中读取数据。

频繁从磁盘存储和读取数据比从内存存储和读取数据耗电更多,而且会增加用户设备的损耗。要知道磁盘使用是否异常,需要了解存储和读取数据的大小,并将其与仪表中观察到的数据进行比较。例如,如果你下载并存储了一个 5 MB 的图片文件用于在经常使用的视图中显示,但却写入了超过 50 MB 的数据,那么请调查云端图像是否经常变化,或者你是否需要配置网络以防止重新下载相同的图像。如果从磁盘读取的数据比预期的要多,请调查内存缓存解决方案是否有帮助,或者你是否从应用或视图生命周期中的错误点启动了数据读取,且读取频率过高。有关减少磁盘写入的更多信息,请参阅[减少磁盘写入](https://developer.apple.com/documentation/xcode/reducing-disk-writes)。

网络 I/O 仪表显示了一段时间内应用从网络读取和写入的数据量。如果应用只使用本地资源,则可能不会从网络读取或写入任何数据。通过网络通信数据会消耗能源并缩短设备的电池寿命,因此应尽可能减少数据传输。要了解应用的网络使用情况,请在应用从网络发送或接收数据时观察网络 I/O 测量值。例如,如果你对下载的图片添加了缓存系统,而网络使用量在访问图片时增加,请确认应用和服务器中的缓存设置是否正确。如果你正在上传用户生成的内容,而在网络状况不佳的情况下频繁上传失败会导致网络使用量增加,那么请构建一个在上传失败时恢复并重新启动,而不是重新上传整个文件的系统。

### 可视化并诊断内存使用量的增加

利用内存图诊断内存泄漏和内存弃用的原因。内存泄漏的明显症状是尽管应用的使用情况表明内存使用正在减少,实际上内存使用量却随着时间的推移持续增加。内存泄漏可能发生在*循环引用*中,即对象之间保持强引用,但应用不再引用它们。这些对象保留在内存中,应用无法删除它们。当你创建了对象且代码仍在引用它们,但应用不再需要或使用它们时,就会发生*内存弃用*。

<img src="https://docs-assets.developer.apple.com/published/298aadf579ae724a68e5cb8051662624/[email protected]">

要在调试器中查看内存图,请在断点处暂停应用,然后单击调试栏中的“调试内存图(Debug Memory Graph)”按钮。或者,在应用运行时单击“调试内存图”按钮,暂停应用并显示内存图。

内存图视图将调试导航器中的堆栈跟踪替换为按库排列的类型列表,每个类型都有一个称为*节点*的实例列表。选择一个节点即可查看其内存图。

节点的内存图显示该节点的所有内存引用,并突出显示强引用。按住 Control 键单击内存图中的任意节点,可执行更多操作,如“快速查看(Quick Look)”或将说明打印到控制台。选择“聚焦节点(Focus on Node)”以显示所选节点的图表。单击引用可查看其详细信息,包括变量名称、引用类型以及内存中的源对象和目标对象。

<img src="https://docs-assets.developer.apple.com/published/6fb1a393aef960969e4b0f56c3fcc176/[email protected]">

要解决循环引用的内存泄漏问题:

1. 在浏览应用时观察内存仪表。
2. 注意当应用实例化对象时,内存使用量会增加,而当你期望系统释放对象时,内存使用量却不减少。
3. 检查内存图,查看对象实例的数量是否出乎意料,或者是否存在不恰当的强引用。
4. 如果存在对某个对象的强引用,请按住 Control 键并单击强引用的节点,然后选择“聚焦节点”查看其图表。如果此节点也有来自对象的强引用,则这是一个循环引用。
5. 解决循环引用的方法是令引用关系的一方使用弱声明来引用另一个对象,或者通过删除对其他对象的所有依赖关系来完全删除引用。重新测试以确认更改是否修复了问题。

要解决内存弃用问题,请确定应用生命周期中不再需要废弃对象的时间,并删除对该对象的所有引用。

### 检查并解决外观和布局问题

某些应用外观或布局方面的问题,只有当你将系统配置为特定界面风格、动态文本大小或当你的应用使用特定辅助功能时才会出现。在以 iOS、macOS 和 tvOS 应用为目标时,请使用环境覆盖在这些环境中测试你的界面。要了解涉及视图位置或大小的问题,可能需要在其他图层的视图上下文中进行检查。在面向 iOS、macOS、tvOS 和 watchOS 应用时,可使用视图调试器显示视图层次结构的 3D 表示形式,来帮助诊断这些问题。visionOS 应用中的实体与其周围环境有时会以你意想不到的方式进行交互。启用可视化来表示坐标轴、边界框和其他通常不可见的信息,这有助于理解这些交互。

有关使用这些功能调试应用外观的信息,请参阅[诊断运行中应用的外观问题](https://developer.apple.com/documentation/xcode/diagnosing-issues-in-the-appearance-of-your-running-app)。
Loading