重拾 ObjC 自動釋放池

Objc 自動釋放池平時不多顯式的使用,但其實它時刻在默默爲咱們工做。關於自動釋放池源碼分析的文章已經不少了,本文不會在源碼層面剖析原理。面試

初衷

在 MRC 時代,須要使用retainrelease手動維護對象的引用計數,並要遵循「誰建立誰釋放」的原則。數據結構

然而在某些場景下沒法知足這個原則,好比說工廠方法:ide

+ (id)factory {
    return [self new];
}
複製代碼

return處若是調用retain,就須要調用方負責 release,這顯然是不科學的設計。因此這裏不能retain, 可是不 retain,該對象超出做用域後就會被釋放,調用方取到的會是 nil。該如何保證調用方在這個對象超出做用域後,還能取到呢?方法就是自動釋放池,在返回前,將該對象被加入自動釋放池,這樣調用方就能順利取到返回值了。oop

那若是對象真的須要被釋放了,如何從自動釋放池裏移除?熟悉 RunLoop 的同窗應該知道,在 RunLoop 喚醒和即將睡眠狀態之間會被插入自動釋放池,每次 RunLoop 迭代都會向本次迭代加入的對象發送一條release 消息。若是對象的引用計數變爲 0,便會被釋放。源碼分析

實現和實踐

自動釋放池雖然被叫作」池「,其實它是一個棧結構。棧的實現方式有不少,自動釋放池採用了雙向鏈表,可以比較方便的實現 push 和 pop。測試

自動釋放池之全部用棧實現,而不用其餘數據結構,好比說散列表?是由於 autorelease 對象每每須要批量處理,好比說一次 RunLoop 迭代生成一大批的 autorelease 對象。ui

因此這裏就出現一個問題,假如某一段代碼在一次 RunLoop 週期內生成大量的 autorelease 對象,尚未等到迭代結束清理,就已經內存溢出了,該怎麼辦?spa

這是就須要手動的來觸發 autorelease 對象的釋放。@autoreleasepool{}登場,它可以控制 autorelease 對象釋放的顆粒度。設計

{
	for (NSInteger i = 0; i < 10000; i++) {
		@autoreleasepool {
		    NSImage *img = [NSImage imageNamed:@"aimge"];
		}
	}
}
複製代碼

上述的極端例子,若是for循環中不嵌套 autoreleasepool,在 Xcode 側邊欄的 Debug Session,能看到應用的佔用的內存不斷的增長。指針

在嵌套使用 autoreleasepool 的場景,而且須要由內而外逐層清理,因此使用棧最適合不過了。

釘子和錘子

之前面試幾乎每次都會被問對象是何時釋放的,通常的回答是引用計數爲 0,但這只是站在對象的角度考慮的,那何時對象的引用計數會變爲 0 呢?

由於瞭解過自動釋放池,因此會說是在 autoreleasepool pop 的時候,若是沒有手動的添加 autoreleasepool 便會在 RunLoop 迭代的時候引用計數被減爲 0 時釋放。

其實回答的有些片面,並非全部對象都是在 pop 的時候引用計數纔會爲 0 的,普通局部對象其實在超出做用域時(大括號)引用計數爲 0,就會被當即釋放。

  1. 普通局部對象 局部對象在超出做用域而且引用計數爲 0 時會當即釋放。
override func viewDidLoad() {
	super.viewDidLoad()
	let o = NSObject()
}
// `o` has release
複製代碼
  1. autorelease 對象
override func viewDidLoad() {
	super.viewDidLoad()
	let imgO = UIImage(named: 「image」);
}
// imgO 還未釋放,會等到 autoreleasepool pop 才釋放。
// 可以使用 weak 指針測試,在 viewWillApper 方法該對象仍然存在
// 或使用符號斷點,檢測 AutoreleasePoolPage::autorelease() 方法被調用
複製代碼
相關文章
相關標籤/搜索