讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 12.5 自動釋放池 >

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:使用自動釋放池時,內存使用保持在穩定的狀態