Swift中的内存泄漏
在本文中,我们将讨论内存泄漏,并将学习如何使用单元测试来检测它们。 偷看一下:
重要:我将解释什么是内存泄漏,讨论保留周期以及您可能已经知道的其他事情。 如果您只想阅读有关单元测试泄漏的信息,请跳至最后一部分。
内存泄漏
确实,这是我们作为开发人员面临的最常见的问题之一。 我们会逐个功能地编写代码,并且随着应用的增长,我们会引入漏洞。
内存泄漏是内存中永远被占用且永远不会再使用的一部分。 这是占用空间并导致问题的垃圾。
在某个时刻已分配但从未释放过并且不再被您的应用程序引用的内存。 由于没有对其的引用,因此现在无法释放它,并且该内存无法再次使用。
苹果文件
从初级到高级开发人员,我们都会在某个时候造成漏洞。 我们有多经验都没关系。 拥有一个干净,无崩溃的应用程序,消除它们是至关重要的。 为什么? 因为它们很危险。
泄漏很危险
它们不仅增加了应用程序的内存占用 ,而且还引入了有害的副作用和崩溃。
为什么内存占用量会增加? 这是对象未释放的直接结果。 这些对象实际上是垃圾。 随着创建这些对象的动作的重复,占用的内存将增加。 垃圾太多了! 这可能会导致出现内存警告情况,最终,该应用程序将崩溃。
解释不必要的副作用需要更多细节。
想象一下在init
内创建通知时开始监听的对象。 它对此做出反应,将内容保存到数据库,播放视频或将事件发布到分析引擎。 由于需要平衡对象,因此我们在释放对象时在deinit
内停止监听通知。
如果此类物体泄漏,会发生什么?
它永远不会死,也永远不会停止收听通知。 每次发布通知时,对象都会对此做出反应。 如果用户重复创建有问题的对象的操作,则将存在多个实例。 所有这些实例都响应该通知并相互介入。
在这种情况下, 崩溃可能是最好的事情。
多个泄漏的对象对应用程序通知做出反应,更改了数据库,UI,破坏了应用程序的整个状态。 您可以在The Pragmatic Programmer中的“死程序不说谎”中了解有关此类问题的重要性。
泄漏无疑会导致不良的用户体验和不良的App Store评分。
泄漏来自哪里?
例如,泄漏可能来自第三方SDK或框架。 甚至来自Apple创建的类,例如CALayer
或UILabel
。 在这些情况下,除了等待更新或放弃SDK之外,我们无能为力。
但是我们很有可能在 我们的代码。 泄漏的 首要 原因是 保持周期 。
为了避免泄漏,我们必须了解内存管理并保留周期。
保持周期
保留一词来自Objective-C中的“手动引用计数天”。 在使用ARC和Swift以及我们现在可以使用值类型做的所有不错的事情之前,有Objective-C和MRC。 您可以在本文中阅读有关MRC和ARC的信息。
那时,我们需要进一步了解内存处理。 理解分配,复制,保留的含义以及如何平衡这些操作与相反的操作(如发布)至关重要。 基本规则是:无论何时创建对象,您都拥有它并负责释放它。
现在事情变得容易得多,但是仍然需要学习一些概念。
在Swift中,当一个对象与另一个对象有很强的关联时,它将保留它。 当我说对象时,我基本上是在谈论引用类型,即类。
结构和枚举是值类型。 无法仅使用值类型创建保留周期。 在捕获和存储值类型(结构和枚举)时,没有诸如引用之类的东西。 尽管值可以保存对对象的引用,但值是复制而不是引用的。
当一个对象引用第二个对象时,它拥有它。 第二个对象将保持活动状态,直到被释放。 这被称为S trong参考 。 只有将属性设置为nil时,第二个对象才会被销毁。
如果A保留B,而B保留A,则存在一个保留循环。
A👉B + A👈B =🌀
在此示例中,将无法取消分配客户端和服务器。
为了从内存中释放,对象必须首先释放其所有依赖关系。 由于对象本身是依赖项,因此无法释放它。 同样, 当对象具有保留周期时,它不会死亡。
当循环中的引用之一较弱或没有所有权时,保留循环将中断。 该循环必须存在,因为它是我们正在编码的关联的性质所必需的。 问题在于,所有关联都不牢固。 其中之一一定是弱者。
如何打破保留周期
使用类类型的属性时,Swift提供了两种解决强引用周期的方法:弱引用和无主引用。
弱引用和无主引用使引用周期中的一个实例可以引用另一个实例, 而无需对其进行严格控制。 然后,这些实例可以相互引用,而无需创建强大的引用周期。
苹果的Swift编程语言
弱:变量可以选择不获取其引用的对象的所有权。 弱引用是当变量不拥有对象所有权时。 弱引用可以为零。
无主的:与弱引用一样,无主的引用也不能对其引用的实例保持强大的控制力。 但是,与弱引用不同,假定无主引用始终具有值。 因此,始终将无主引用定义为非可选类型。 无主引用不能为零。
何时使用每个:
当闭包及其捕获的实例始终相互引用并且始终在同一时间解除分配时,将闭包中的捕获定义为无主引用。
相反,当捕获的参考在将来的某个时刻可能变为
nil
,将捕获定义为弱参考。 弱引用始终是可选类型,并且在释放它们引用的实例时自动变为nil
。
苹果的Swift编程语言
在编写代码的过程中,常常会在某个地方忘记weak self
。 我们通常在编写块闭包时引入泄漏,例如在反应性代码内部编写flatMap
和map
时,或者在编写观察者或委托时。 在本文中,您可以阅读有关闭包泄漏的信息。
如何消除内存泄漏?
- 不要创建它们。 对内存管理有深刻的了解。 为您的项目定义强大的代码样式,并尊重它。 如果您整洁并尊重自己的代码风格,那么缺乏
weak self
就会很明显。 核心评论确实可以提供帮助。 - 使用Swift Lint。 这是一个强大的工具,可强制您遵守代码风格并遵守规则1。它可帮助您在编译时发现早期问题。 就像不弱的委托变量声明一样,它成为潜在的保留周期。
- 在运行时检测泄漏并使其可见。 如果您知道一次必须有几个实例处于活动状态,则可以使用LifetimeTracker。 在开发模式下运行是一个很棒的工具。
- 经常分析应用程序。 XCode附带的内存分析工具做得很好。 请参阅本文。 乐器曾经是不久前的发展方式,它也是很好的工具。
- 使用SpecLeaks进行单元测试泄漏。 该Pod使用Quick and Nimble,可让您轻松创建泄漏测试。 您可以在下一节中阅读有关它的信息。
单元测试泄漏
一旦知道了循环和弱引用的工作原理,就可以编写代码来测试保留循环。 这个想法是使用弱引用来探测周期。 借助对对象的弱引用,我们可以测试该对象是否泄漏。
因为弱引用不能完全保持它所引用的实例,所以有可能在弱引用仍在引用该实例时将其释放。 因此, 当释放它所引用的实例时 , ARC会自动将弱引用设置为
nil
。
假设我们要查看对象x
泄漏。 我们可以为其创建一个弱引用,并将其称为leakReferece.
如果从内存中释放了x
,则ARC会将leakReference
设置为nil。 因此,如果x
泄漏, leakReferece
永远不会为零。
如果x
实际上在泄漏,则弱变量leakReference
将指向泄漏的实例。 另一方面,如果对象没有泄漏,则将其设置为nil后,就不应再存在。 在这种情况下, leakReference
将为零。
“ Sundell的Swift”在本文中详细介绍了各种类型的泄漏。 该帖子对我写这篇文章和SpecLeaks确实很有帮助。 遵循类似方法的另一篇不错的文章。
基于此概念,我创建了SpecLeaks,这是Quick and Nimble的扩展,可让您测试泄漏。 想法是对泄漏进行单元测试,而无需编写很多样板代码。
规格泄漏
Quick and Nimble是一种以更易于理解的方式编写单元测试的绝佳组合。 SpecLeaks仅是这些框架的一部分,使您可以创建单元测试来查看对象是否泄漏。
如果您不了解单元测试,则此屏幕快照可能会提示您它的作用: