如何在 Swift 中识别强引用循环?

Posted

技术标签:

【中文标题】如何在 Swift 中识别强引用循环?【英文标题】:How can identify strong reference cycles in Swift? 【发布时间】:2015-11-22 14:11:25 【问题描述】:

是否有工具或方法可以在我的 SWIFT 代码中定位强引用循环?

强引用循环是指类的两个实例在没有适当安全措施的情况下相互引用 (weak/unowned),因此一旦我创建的所有变量停止引用这些对象,垃圾收集器就无法处理它们。

【问题讨论】:

我是这个生态系统的新手(所以不了解 Objective C),所以,从现在开始就是 ARC。 没关系,因为概念大致相同。如果您在 Objective-C 强引用周期中看到讨论并且无法立即将其应用到您的 Swift 编程中,我会感到惊讶(尤其是因为您熟悉 Swift 中的unownedweak 语法)。 【参考方案1】:

在 Swift 中查找强引用循环的方法与在 Objective-C 中的方法相同。

您将从 Xcode 运行应用程序,充分锻炼应用程序以显示循环,然后点击“调试内存图”按钮 ()。然后,您可以在左侧面板中选择一个未释放的对象,它会向您显示内存图,通常可以清楚地显示强引用循环:

有时内存周期并不那么明显,但您至少可以看到哪个对象保持对相关对象的强引用。如有必要,您可以向后跟踪并确定是什么保持了对它的强引用,依此类推。

有时知道什么样的对象保持强引用是不够的,你真的想知道强引用是在代码中的什么位置建立的。 “malloc stack”选项,如https://***.com/a/30993476/1271826 所示,可用于识别建立此强引用时调用堆栈是什么(通常让您识别建立这些强引用的精确代码行)。欲了解更多信息,请参阅 WWDC 2016 视频Visual Debugging with Xcode。

您还可以使用 Instruments 来识别泄漏的对象。只需使用 Allocations 工具通过 Instruments 运行应用程序,反复(不仅仅是一次或两次)将应用程序返回到某个稳定状态条件,如果内存继续增加,那么您可能有一个强大的参考周期。您可以使用分配工具来识别哪些对象没有被释放,使用“记录引用计数”功能来准确识别这些强引用的建立位置等。

有关识别和解决内存问题的介绍,请参阅 WWDC 2013 视频 Fixing Memory Issues 和 WWDC 2012 视频 ios App Performance: Memory。那里提出的基本技术今天仍然适用(尽管 Instruments 工具的 UI 发生了一些变化……如果您想了解稍微改变的 UI,请参阅 WWDC 2014 视频Improving Your App with Instruments)。

顺便说一句,“垃圾收集”指的是一种非常不同的内存系统,在这里并不适用。

【讨论】:

【参考方案2】:

您可以将 deinit 函数添加到您的类中,这些函数将在您的对象被释放时被调用。

如果在您的应用运行时未调用 deinit,您可以按下“调试内存图”按钮(在下方圈出)并检查哪些内容对哪些内容有引用。

使用中间窗格顶部的下拉菜单在类和类实例之间切换。

如果某些东西被一遍又一遍地分配而没有被释放,您应该会看到多个实例,并且您应该能够通过方向图看到它的一个孩子是否持有对其父母的强引用。

【讨论】:

【参考方案3】:

非常简单的方法是在 deinitialiser 中放置一个 print

deinit 
   print("<yourviewcontroller> destroyed.")

确保您看到此行打印在控制台上。将 deinit 放入所有视图控制器中。如果您无法看到特定的视图控制器,则意味着它们是一个参考循环。可能的原因是委托很强大,闭包捕获了自我,计时器没有被入侵,等等。

【讨论】:

对于这种方法,我将添加一个手动“二进制搜索”:禁用整个代码部分,并确保调用 deinit。重新启用一半代码,并检查是否仍调用 deinit 或是否未调用。递归 ;) 在 Swift 中,因为创建内联闭包非常容易,所以在其中创建引用循环的可能性也更高。注意代码中的任何闭包。为了安全起见,我通常以 [weak self] 守卫 let weakSelf = self else return 开始我的闭包。阅读developer.apple.com/library/ios/documentation/Swift/Conceptual/…【参考方案4】:

使用仪器检查泄漏和内存丢失。在 Instruments 上的 Allocations 工具中使用标记生成(Heapshot)。

有关如何使用 Heapshot 查找内存占用,请参阅:bbum blog

基本上,该方法是运行 Instruments allocate 工具,获取一个 heapshot,运行代码的迭代并获取另一个 heapshot,重复 3 或 4 次。这将指示在迭代期间已分配但未释放的内存。

要弄清楚结果,请查看各个分配。

如果您需要查看对象的保留、释放和自动释放发生的位置,请使用工具:

在仪器中运行,在分配中设置“记录引用计数”(对于 Xcode 5 及更低版本,您必须停止记录才能设置选项)。使应用程序运行、停止录制、向下钻取,您将能够看到所有保留、释放和自动释放发生的位置。

【讨论】:

第一句见:“标记生成”。 bbum 的博客文章使用了“Heapshot”,所以我将它包含在括号中:“(Heapshot)”。【参考方案5】:

您可以use Instruments 这样做。正如this article 的最后一段所说:

一旦 Instruments 打开,您应该启动您的应用程序并进行一些交互,特别是在您要测试的区域或视图控制器中。任何检测到的泄漏都将在“泄漏”部分显示为红线。助手视图包括一个区域,Instruments 将向您显示泄漏中涉及的堆栈跟踪,让您深入了解问题所在,甚至允许您直接导航到有问题的代码。

【讨论】:

以上是关于如何在 Swift 中识别强引用循环?的主要内容,如果未能解决你的问题,请参考以下文章

Swift如何实现通用类型的弱引用数组(上)

Swift如何实现通用类型的弱引用数组(下)

如何在Swift中识别出强大的参考周期?

从 Objective-C 桥接到 swift 时,swift 编译器如何识别变量是复制的还是强的?

iOS开发-Swift进阶之内存管理 & Runtime!

如何避免在Block里用self造成循环引用