这个问题已经开了很长时间,我现在觉得足够自信来回答它。
内存管理的不同层次:
硬件内存
在 Swift 中,使用 ARC 时,我们没有办法清理实际的硬件内存。我们只能让操作系统为我们完成此操作。其中一部分是使用正确的代码(可选项
和 弱引用
),另一部分是为操作系统创建时间完成其工作。
想象一下,我们有一个在所有线程上无限运行的函数。它只会执行一件事情,即加载图像、将其转换成黑白并保存。
所有图像最大为几 mb,该函数不会创建任何软件内存泄漏。
由于图像没有固定大小且可能具有不同的压缩,它们的占用空间也不相同。
此函数将始终使您的应用程序崩溃。
这种“硬件”内存泄漏是由于该函数始终占用下一个可用的内存槽引起的。
因为没有空闲时间,所以操作系统不会介入“实际清理内存”。在每次传递之间放置延迟可以完全解决此问题。
特定语言的内存管理
类型转换
某些操作对内存没有影响,而其他操作则有:
let myInt : Int = 1
Float(myInt) // this creates a new instance
尝试使用类型转换:
(myInt as Float) // this will not create a new instance.
引用类型 vs 值类型 | 类 vs 结构体
两者都有其优点和危险。
结构体由于是
值类型,所以占用内存较大。这意味着当赋给另一个实例时,它们会
复制它们的值,有效地
将内存使用量翻倍。对此没有修复/解决方法。这就是使结构体成为结构体的原因。
类没有这种行为,因为它们是
引用类型。它们在赋值时不进行复制。相反,它们创建对
同一对象的另一个引用。
ARC或
自动引用计数负责跟踪这些引用。每个对象都有一个引用计数器。每次将其赋值时,计数器加一。每次将一个引用设置为 nil、封闭函数结束或封闭对象 deinit 时,计数器减一。
当计数器达到0时,对象会被析构。
有一种方法可以防止实例析构,从而创建泄漏。这称为
强引用循环。
这里有一个关于弱引用的好解释
class MyClass {
var otherClass : MyOtherClass?
deinit {
print("deinit")
}
}
class MyOtherClass {
var myclass : MyClass?
deinit {
print("deinit")
}
}
var classA : MyClass? = MyClass()
classA!.otherClass = MyOtherClass()
classA!.otherClass!.myclass = classA
classA = nil
将一个引用设置为弱引用
class MyOtherClass {
weak var myclass : MyClass?
deinit {
print("deinit")
}
}
输入输出
函数可以接收传递给它们的值。但也可以将这些值标记为“inout”。这使您能够更改传递给函数的结构体,而无需复制该结构体。这样做可以节省内存,具体取决于您传递的内容和在函数中执行的操作。
此外,使用“inout”还可以很好地实现多个返回值,而无需使用元组。
var myInt : Int = 0
func inoutTest(inout number: Int) {
number += 5
}
inoutTest(&myInt)
print(myInt)
func addTest(number:Int) -> Int {
return number + 5
}
函数式编程
状态是随时间变化的值。
函数式编程是面向对象编程的对应物。函数式编程使用不可变状态。
更多信息请参见此处。
面向对象编程使用具有变化/可变状态的对象。而不是创建新值,旧值被更新。
函数式编程可能会使用更多内存。
示例:FP
可选类型
可选类型允许将事物设置为nil。这将降低类的引用计数或使结构体失效。将事物设置为nil是清理内存的最简单方法。这与ARC相辅相成。一旦将类的所有引用都设置为nil,它将失效并释放内存。
如果您不将实例创建为可选类型,则数据将保留在内存中,直到封闭函数结束或封闭类失效。您可能不知道何时会发生这种情况。可选类型可以控制什么东西存活多长时间。
API MM
许多“内存泄漏”是由您可能没有调用的“清理”函数引起的框架。
一个很好的例子是UIGraphicsEndImageContext()
。上下文将保留在内存中,直到调用此函数。当创建上下文的函数结束或涉及的图像设置为nil时,它不会清除。
另一个很好的例子是dismiss ViewControllers。从一个VC segue到另一个VC,然后再segue回来可能是有意义的,但是segue实际上创建了一个VC。segue返回不会销毁VC。调用dismissViewControllerAnimated()
将其从内存中删除。
阅读类参考并仔细检查是否有“清理”函数。
如果确实需要Instruments找到泄漏,请查看本问题的其他答案。