Swift 管理内存

Posted

技术标签:

【中文标题】Swift 管理内存【英文标题】:Swift Managing Memory 【发布时间】:2015-03-13 12:37:49 【问题描述】:

此问题已清理,重要信息移至下面的答案。


我有一些关于内存管理的问题。

我正在构建一个照片编辑应用程序。所以保持低内存使用很重要。 此外,我不会发布代码,因为在做一件特定的事情时我没有大的内存泄漏。我只是因为发生的一切而失去了几个 KB/MB。并且通过数万行代码来查找千字节并不好玩;)

我的应用使用核心数据、大量 cifilter 内容、位置和基础知识。

我的第一个视图只是一个 tableview,它花费了我大约 5mb 的内存。 然后你拍一些照片,应用一些过滤器,这会保存到核心数据中,然后你回到第一个视图。

是否有可能真正摆脱内存中的所有内容,除了驱动第一个视图所需的数据。 (非常节省和令人敬畏的 5mb)

或者即使你将所有内容都设置为 nil,也会留下一些东西?


额外问题: UIImageJPEGRepresentationUIImagePNGRepresentation 之间的文件大小/cpu 负载是否存在差异? 我知道您可以使用 JPEG 方法设置压缩质量(在 cpu/gpu 上更难?)。

只是想尽一切可能减少内存压力。


更新:

有人向我指出这个问题可能太模糊了。

我在某些时候遇到的问题如下:

在某些时候,峰值内存使用率过高 导航到第二个视图控制器并返回会导致泄漏 编辑图像会导致内存泄漏。 将过滤器应用于超过 4-5 个图像会由于内存不足而导致崩溃,此时没有更多的内存泄漏。 (在仪器中验证)

P.s 这都是在 iPhone 4s 上测试的,不是模拟器。

这里有一个模因来减轻这个网站的气氛。

【问题讨论】:

@brian 感谢编辑! ACR 来自 adobe camera raw 和多年的摄影师生涯。尽管我知道它代表什么,但我总是将其视为 ACR :) “ARC 只有在内存压力超过某个阈值时才会真正介入” – 不,ARC 是自动引用计数,而不是垃圾收集器。 必须尝试在 Instruments 中使用 snapshots 来定位“消失的记忆”? 你说你使用核心数据。当不再需要它们时,您可能会尝试围绕 faulting 核心数据托管对象的使用进行调查。 我可能是错的,但我不相信当有内存压力时是 ARC 介入;相反,我相信它是 ios 本身。您的视图控制器将收到 didReceiveMemoryWarning 方法,您应该在那里做出相应的响应。 ARC 管理应用程序分配的对象的引用数量,并将对象设置为 nil,并在它们的引用计数为零时释放它们。 【参考方案1】:

这个问题已经开放了很长时间,我现在有足够的信心回答它。


MM的不同等级:

硬件内存

在带有 ARC 的 Swift 中,我们无法清理实际的硬件内存。我们只能让操作系统为我们做到这一点。一方面是使用正确的代码(optionalsweak),另一方面是为操作系统创造时间来完成它的工作。

想象一下,我们有一个无限期地在所有线程上运行的函数。它只做一件事,加载图像,转换为黑白并保存。 所有图像的最大值为几 mb,并且该功能不会产生软件内存泄漏。 因为图像没有固定的大小并且可能有不同的压缩,所以它们没有相同的占用空间。 此功能将始终使您的应用程序崩溃。

这种“硬件”内存泄漏是由函数总是占用下一个可用的内存插槽引起的。

操作系统不会介入“实际清理内存”,因为没有空闲时间。在每次通过之间设置延迟完全解决了这个问题。


语言特定的MM

选角

有些操作对内存没有影响,有些则有:

let myInt : Int = 1
Float(myInt) // this creates a new instance

尝试转换:

(myInt as Float) // this will not create a new instance.

引用类型与值类型 |类与结构

两者各有优劣。

结构是内存密集型的,因为它们是值类型。 这意味着它们在分配给另一个实例时复制它们的值,从而有效地将内存使用量加倍。 对此没有修复/解决方法。这就是 Structs 结构的原因。

没有这种行为,因为它们是引用类型。他们在分配时不会复制。 相反,他们为同一对象创建另一个引用ARCAutomatic Reference Counting 用于跟踪这些引用。 每个对象都有一个引用计数器。每次分配它,它都会增加一个。每次你设置一个对 nil 的引用时,封闭的函数结束,或者封闭的 Object deinits,计数器就会下降。

当计数器达到 0 时,对象被取消初始化。

有一种方法可以防止实例取消初始化,从而造成泄漏。这称为强参考循环

Good explanation of Weak

class MyClass 

    var otherClass : MyOtherClass?

    deinit 
        print("deinit") // never gets called
    


class MyOtherClass 

    var myclass : MyClass?

    deinit 
        print("deinit") // never gets called
    


var classA : MyClass? = MyClass()

// sorry about the force unwrapping, don't do it like this
classA!.otherClass = MyOtherClass()
classA!.otherClass!.myclass = classA // this looks silly but in some form this happens a lot

classA = nil
// neither the MyClass nor the MyOtherClass deinitialised and we no longer have a reference we can acces. Immortalitiy reached they have.

设置一个对weak的引用

class MyOtherClass 

    weak var myclass : MyClass?

    deinit 
        print("deinit") // gets called
    


输入

函数捕获传递给它们的值。但也可以将这些值标记为 inout。这允许您更改传递给函数的结构,而无需复制结构。这可能会节省内存,具体取决于您传递的内容以及您在函数中执行的操作。

这也是一种无需使用元组即可获得多个返回值的好方法。

var myInt : Int = 0

// return with inout
func inoutTest(inout number: Int) 

    number += 5



inoutTest(&myInt)
print(myInt) // prints 5

// basic function with return creates a new instance which takes up it's own memory space
func addTest(number:Int) -> Int 

    return number + 5



函数式编程

状态是随时间变化的价值

函数式编程是面向对象编程的对应部分。函数式编程使用不可变状态。

更多关于here

面向对象编程使用具有变化/变异状态的对象。不是创建新值,而是更新旧值。

函数式编程可以使用更多内存。

example on FP


选项

选项允许您将事物设置为零。这将降低类的引用计数或取消初始化结构。将事物设置为 nil 是清理内存的最简单方法。这与 ARC 齐头并进。一旦你将一个类的所有引用都设置为 nil,它将 deinit 并释放内存。

如果您不将实例创建为可选项,则数据将保留在内存中,直到封闭函数结束或封闭类取消初始化。你可能不知道这什么时候会发生。选项让您可以控制哪些内容可以存活多久。


API MM

许多“内存泄漏”是由具有您可能没有调用的“清理”功能的框架引起的。 一个很好的例子是UIGraphicsEndImageContext() 上下文将保留在内存中,直到调用此函数。当创建上下文的函数结束时,或者所涉及的图像设置为 nil 时,它不会清理。

另一个很好的例子是关闭 ViewControllers。转入一个 VC 然后转回可能是有意义的,但转义实际上创建了一个 VC。 segue back 不会破坏 VC。调用dismissViewControllerAnimated() 将其从内存中删除。

阅读类引用并仔细检查没有“清理”功能。


如果您确实需要仪器来查找泄漏,请查看此问题的其他答案。

【讨论】:

这个答案在很多层面都具有误导性,有些句子是错误的。有些部分甚至与主题没有任何关系(例如关于函数式编程的那段也是完全错误的)。 @Sulthan 再次检查了一遍。我将有关函数式编程的部分缩短为基本思想,并添加了指向更多信息的链接。函数式编程确实倾向于使用更多的内存。当我开始编程时,我问了这个问题,它非常广泛:“如何快速释放内存以及如何防止使用过多的内存”。这个答案涵盖了大部分。如果您反对并说这是错误的,请提供来源。答案的很大一部分首先作为更新包含在问题中。 7 个赞成票可能是人们发现它有用且正确的结果。最近我把它移到了这个答案。 @Sulthan 我想要的只是在需要时改进答案,所以我很认真地询问哪些部分是错误的/误导的。我不固执,犯错时能接受。您能否提供更多反馈,而不仅仅是投反对票并发表负面评论? 函数式编程不必依赖不可变状态。有些语言是函数式的并且依赖于不可变状态,但这并不意味着这两个概念必然相关。 “硬件内存泄漏”是一个非常奇怪的术语......操作系统(OS)与应用程序级别的内存管理没有任何关系。整个答案对于详细审查来说有点大,但相信我,审查是必要的。最大的问题是实际问题不清楚,这可能是它没有作为副本关闭的唯一原因。 @Sulthan 硬件内存泄漏可能不是一个完美的术语,但就是这样。某些操作使内存无法使用,直到有空闲时间将其清理干净。这相当于内存泄漏。这是我第一次问这个问题时遇到的最大问题之一。函数式编程可能不依赖于不可变状态,但这个概念通常与之相关。我可以删除术语函数式编程并将其纯粹描述为可变与不可变?过了这么久才结束一个比较受欢迎的问题是不是有点过激?【参考方案2】:

点击 Xcode 右上角的应用名称。

在弹出的菜单中点击“编辑方案”。

确保选择左侧的“运行”,然后单击窗口顶部附近的诊断选项卡。

在“内存管理”标题下检查“启用 Guard Malloc”

您可能还想尝试检查“日志记录”标题下的“分布式对象”和“malloc 堆栈”

更多关于保护 malloc、保护边缘和 scribble 的信息可以在here找到。

希望这会有所帮助!

【讨论】:

对不起,我有一个困惑。这样 xcode 禁用内存使用。它有什么好处?是隐藏显示内存使用还是减慢内存使用?

以上是关于Swift 管理内存的主要内容,如果未能解决你的问题,请参考以下文章

Swift 内存管理与异常处理

Swift 管理内存

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

Swift之深入解析内存管理的底层原理

swift内存管理中的引用计数

Swift 内存管理:在 var 中存储 func