Swift 管理内存
Posted
技术标签:
【中文标题】Swift 管理内存【英文标题】:Swift Managing Memory 【发布时间】:2015-03-13 12:37:49 【问题描述】:此问题已清理,重要信息移至下面的答案。
我有一些关于内存管理的问题。
我正在构建一个照片编辑应用程序。所以保持低内存使用很重要。 此外,我不会发布代码,因为在做一件特定的事情时我没有大的内存泄漏。我只是因为发生的一切而失去了几个 KB/MB。并且通过数万行代码来查找千字节并不好玩;)
我的应用使用核心数据、大量 cifilter 内容、位置和基础知识。
我的第一个视图只是一个 tableview,它花费了我大约 5mb 的内存。 然后你拍一些照片,应用一些过滤器,这会保存到核心数据中,然后你回到第一个视图。
是否有可能真正摆脱内存中的所有内容,除了驱动第一个视图所需的数据。 (非常节省和令人敬畏的 5mb)
或者即使你将所有内容都设置为 nil,也会留下一些东西?
额外问题:
UIImageJPEGRepresentation
和 UIImagePNGRepresentation
之间的文件大小/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 中,我们无法清理实际的硬件内存。我们只能让操作系统为我们做到这一点。一方面是使用正确的代码(optionals
和weak
),另一方面是为操作系统创造时间来完成它的工作。
想象一下,我们有一个无限期地在所有线程上运行的函数。它只做一件事,加载图像,转换为黑白并保存。 所有图像的最大值为几 mb,并且该功能不会产生软件内存泄漏。 因为图像没有固定的大小并且可能有不同的压缩,所以它们没有相同的占用空间。 此功能将始终使您的应用程序崩溃。
这种“硬件”内存泄漏是由函数总是占用下一个可用的内存插槽引起的。
操作系统不会介入“实际清理内存”,因为没有空闲时间。在每次通过之间设置延迟完全解决了这个问题。
语言特定的MM
选角
有些操作对内存没有影响,有些则有:
let myInt : Int = 1
Float(myInt) // this creates a new instance
尝试转换:
(myInt as Float) // this will not create a new instance.
引用类型与值类型 |类与结构
两者各有优劣。
结构是内存密集型的,因为它们是值类型。 这意味着它们在分配给另一个实例时复制它们的值,从而有效地将内存使用量加倍。 对此没有修复/解决方法。这就是 Structs 结构的原因。
类没有这种行为,因为它们是引用类型。他们在分配时不会复制。 相反,他们为同一对象创建另一个引用。 ARC 或 Automatic 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 管理内存的主要内容,如果未能解决你的问题,请参考以下文章