Swift中的内存管理:调试问题

这是最后一篇文章,是有关Swift中的内存管理的三篇文章系列。 如果您不熟悉Swift中的内存管理,第一篇文章将介绍所有内容! 相反,如果您正在寻找常见问题,则可以在此处阅读第二篇文章。

Xcode提供了两种开箱即用的工具来调试复杂的保留周期:

  • 内存图调试器
  • 内存泄漏仪

第一个更加用户友好,但有时可能会忽略应用程序中的某些问题。 本文将展示如何同时使用它们,以及何时使用它们。 我还将分享另一个“技术”,在这里我称之为“分而治之”,但这实际上是找到特别难以捉摸的保留周期的最后手段。


让我们来看一个简单的例子,一个视图控制器意外地通过委托保留了自己:

让我们启动Memory Graph Debugger,看看它有什么要说的。 当应用程序正在运行时,可以在Xcode底部的工具栏中使用Memory Graph Debugger。 它有2条线连接的3个点:

启动后,请确保您位于右侧的标签(右侧第二个标签)中:

它将在左面板中显示该应用程序中当前存在的所有对象(数量很多!)。

Memory Graph Debugger有时很方便,但是即使对于诸如此类的简单泄漏,它也无法正确检测到它。 如果您知道某个对象没有在适当的时候被释放(例如,通过在deinit放置一个断点并观察到没有停止的地方)。

在底角,您可以输入搜索词并使用您感兴趣的对象进行过滤-这是我们的ViewController

单击它会显示引用ViewController

在这里,我们可以观察到MyView属性强烈引用了ViewController

如预期的那样,使用weak引用( weak var delegate: MyViewDelegate? )解决了该问题,并且ViewController得到了正确的释放。

泄漏仪器是比Memory Graph Debugger更高级的工具。 但是,尽管功能更强大,但也更难使用。 使用与上述相同的示例,使用“泄漏仪器”运行该应用程序将得到以下结果:

泄漏检测工具记录在应用程序中创建和删除的每个对象的内存使用情况引用计数 。 给定对象的实例,仪器可以让您知道何时创建对象,何时释放对象(如果有的话)以及何时增加或减少其引用计数。 例如,给定我们之前的ViewController实例,如果我们寻找它:

然后我们可以选择它,然后点击它旁边的小箭头:

该视图将过渡到另一个包含该类型的所有已释放实例和活动实例的视图:

在这里,我们可以看到我们只有一个实例,并且它仍然存在-肯定在那里发生了事情!

如果再追溯一次,我们将访问该实例的引用计数历史记录,然后由您确定为什么其引用计数从未达到0。

该工具较难使用的原因之一是可能有成千上万的引用计数更改。 默认情况下,仪器将尝试将保留/释放分组在一起,这可能会导致非时间顺序的参考计数更改。 如果您希望按时间顺序进行更改,请务必在开始时选择“按时间”。 您也可以通过单击“未配对”来取消对更改的配对。

单击一行将在发生时显示其完整堆栈跟踪,从而使您能够确定发生的位置和时间。 通常,您将在自己的代码中查找问题,因此您应假定“ Responsible Library将是您应用程序的名称。 这是引用计数为我们增加的行,没有相应的dealloc

双击右侧堆栈跟踪中的ViewController.viewDidLoad()行,直接将我们引向该行。

这是我发现自己(和我的同事)做过几次的事情,当发生保留周期时,Memory Graph Debugger或Leak Instrument都没有帮助。 这通常发生在应用程序中经常使用的视图中,并且保留周期仅在某些用户输入时发生。

这种方法包括简单地删除代码段,直到保留周期消失为止;一旦消失,则将它们重新添加回原来的位置,直到找到导致它的行。 因此,这是一种“强力”方法,仅当其他调试尝试失败时才应将其用作最后手段。

这种方法的另一种变体是使用git bisect来帮助确定未发生周期的特定提交以及引入了有害更改的提交。

您已到达本系列的结尾!

我希望所有或部分文章对您有所帮助,并且您已经了解了一两个关于Swift中如何管理内存以及如何正确管理内存的知识。

如果您有任何意见,请不要犹豫!