首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》12.5 自动释放池

关灯直达底部

当一个方法创建了一个实例并将其返回时,一些内存管理技巧就要派上用场了。比如,考虑如下简单代码:


func makeImage -> UIImage? {    if let im = UIImage(named:/"myImage/") {        return im    }    return nil}  

思考一下返回的UIImage类型的im的保持计数。调用UIImage的初始化器UIImage(named:)会增加其保持计数。根据内存管理的黄金法则,通过函数返回让im脱离我们自己的控制时,我们应该减少它的保持计数,从而平衡之前的增加并交出所有权。不过应该什么时候做呢?如果在return im这一行之前做,那么im的保持计数就会为0,它将被销毁;函数将会返回一个野指针。不过也不能在return im这一行之后做,因为当这行代码执行时,函数代码就宣布执行完毕了。

显然,我们需要通过一种方式来返回这个对象,现在不会减少其保持计数(这样在调用者接收并处理它时,它就会一直存在),同时又要确保在未来的某一时刻我们可以减少其保持计数,从而平衡对其的init(named:)调用,并实现对该对象内存的管理。解决之道就是介于释放对象与不释放对象之间的一种策略,即ARC会自动释放它。

下面来介绍一下自动释放的工作原理。你的代码运行时会有一个自动释放池存在。当ARC自动释放对象时,该对象会被放到自动释放池当中,并且一个数字会增加,这个数字表示该对象被放到这个自动释放池当中的次数。时不时地,当没有其他事情发生时,自动释放池会被自动清空。这意味着自动释放池会释放其中的每一个对象、清除对象被添加到自动释放池中的次数,并清空所有对象。如果这导致对象的保持计数变为0,那么对象就会像通常那样被销毁。因此,自动释放一个对象就好比是释放它,但带有一个附加条款,即“稍后再释放,而不是此时此刻”。

一般来说,自动释放与自动释放池只不过是一种实现细节而已。你看不到其实现;它们只是ARC工作过程的一部分而已。那我为何还要介绍它们呢?这是因为,有时(非常少见)你想要自己来清空自动释放池。考虑如下代码(代码是我编造出来的,因为演示清空自动释放池并不是那么容易):


func test {    let path = NSBundle.mainBundle.pathForResource(/"001/", ofType: /"png/")!    for j in 0 ..< 50 {        for i in 0 ..< 100 {            let im = UIImage(contentsOfFile: path)         }    }}  

该方法所做的事情并没有什么实际意义;它会加载一张图片,不过是在一个循环中重复加载。循环运行时,内存占用量在持续攀升(如图12-1所示);当方法执行完毕时,应用的内存使用量已经达到了约34MB。这并不是因为每次循环遍历时没有释放图片,而是因为存在着大量的中间对象(一些你从来没听说过的对象,如NSPathStore2对象),它们都是在调用init(contentsOfFile:)时生成的,它们会被自动释放,因此都在那儿等着,导致自动释放池中的对象越来越多,它们在等待自动释放池被清空。当代码执行完毕时,自动释放池会被清空,内存使用量会迅速向下跌落,直到基本没有多少内存被使用。

图12-1:循环中的内存使用增长

当然,34MB的内存并不算大。不过,可以想象的是更加复杂的内部循环会产生更多、更大的自动释放对象,内存使用也会持续增长。这样,要是能手工清空自动释放池,然后在循环过程中不断清空就好了。Swift提供了这种方式:全局的autoreleasepool函数,它接收一个参数,这个参数是个匿名函数。在调用匿名函数前会创建一个特殊的临时自动释放池,它用于随后自动释放的对象。当该匿名函数执行完毕时,这个临时自动释放池会被清空并销毁。下面这个方法与之前一样,不过使用了autoreleasepool调用包装了内部循环:


func test {    let path = NSBundle.mainBundle.pathForResource(/"001/", ofType: /"png/")!    for j in 0 ..< 50 {        autoreleasepool {            for i in 0 ..< 100 {                let im = UIImage(contentsOfFile: path)            }        }    }}  

内存使用上的差异是非常明显的:内存占用量稳定在2MB以下(如图12-2所示)。创建与清空临时的自动释放池可能会有一些成本,因此如果可能,你需要将循环划分为一个外部循环与一个内部循环,如该示例所示,这样每次迭代时就不会再创建和销毁自动释放池了。

图12-2:使用自动释放池时,内存使用保持在稳定的状态