Objective-C 內(nèi)存管理
在 Objective-C 中留量,對象通常是使用 alloc 方法在堆上創(chuàng)建的搀缠。 [NSObject alloc] 方法會在對堆上分配一塊內(nèi)存旺隙,按照NSObject的內(nèi)部結(jié)構(gòu)填充這塊兒內(nèi)存區(qū)域。
一旦對象創(chuàng)建完成穆刻,就不可能再移動它了。因為很可能有很多指針都指向這個對象妄讯,這些指針并沒有被追蹤。因此沒有辦法在移動對象的位置之后更新全部的這些指針酷宵。
MRC 與 ARC
Objective-C中提供了兩種內(nèi)存管理機制:MRC(MannulReferenceCounting)和 ARC(Automatic Reference Counting)亥贸,分別提供對內(nèi)存的手動和自動管理,來滿足不同的需求〗娇眩現(xiàn)在蘋果推薦使用 ARC 來進行內(nèi)存管理炕置。
MRC
autorelease 使得對象在超出生命周期后能正確的被釋放(通過調(diào)用release方法)。在調(diào)用 release 后男韧,對象會被立即釋放朴摊,而調(diào)用 autorelease 后,對象不會被立即釋放此虑,而是注冊到 autoreleasepool 中甚纲,經(jīng)過一段時間后 pool結(jié)束,此時調(diào)用release方法朦前,對象被釋放介杆。
在MRC的內(nèi)存管理模式下,與對變量的管理相關(guān)的方法有:retain, release 和 autorelease韭寸。retain 和 release 方法操作的是引用記數(shù)春哨,當引用記數(shù)為零時,便自動釋放內(nèi)存恩伺。并且可以用 NSAutoreleasePool 對象赴背,對加入自動釋放池(autorelease 調(diào)用)的變量進行管理,當 drain 時回收內(nèi)存晶渠。
ARC
自動內(nèi)存管理機制凰荚,會根據(jù)引用計數(shù)自動監(jiān)視對象的生存周期,實現(xiàn)方式是在編譯時期自動在已有代碼中插入合適的內(nèi)存管理代碼以及在 Runtime 做一些優(yōu)化乱陡。
__strong 是默認使用的標識符浇揩。只有還有一個強指針指向某個對象,這個對象就會一直存活憨颠。
__weak 聲明這個引用不會保持被引用對象的存活,如果對象沒有強引用了积锅,弱引用會被置為 nil
__unsafe_unretained 聲明這個引用不會保持被引用對象的存活爽彤,如果對象沒有強引用了,它不會被置為 nil缚陷。如果它引用的對象被回收掉了适篙,該指針就變成了野指針。
__autoreleasing 用于標示使用引用傳值的參數(shù)(id *)箫爷,在函數(shù)返回時會被自動釋放掉嚷节。
引用循環(huán)
當兩個對象互相持有對方的強引用聂儒,并且這兩個對象的引用計數(shù)都不是0的時候,便造成了引用循環(huán)硫痰。
要想破除引用循環(huán)衩婚,可以從以下幾點入手:
- 注意變量作用域,使用 autorelease 讓編譯器來處理引用
- 使用弱引用(weak)
- 當實例變量完成工作后效斑,將其置為nil
Autorelease Pool
Autorelase Pool 提供了一種可以允許你向一個對象延遲發(fā)送release消息的機制非春。當你想放棄一個對象的所有權(quán),同時又不希望這個對象立即被釋放掉(例如在一個方法中返回一個對象時)缓屠,Autorelease Pool 的作用就顯現(xiàn)出來了奇昙。
所謂的延遲發(fā)送release消息指的是,當我們把一個對象標記為autorelease時:
NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease];
這個對象的 retainCount 會+1敌完,但是并不會發(fā)生 release储耐。當這段語句所處的 autoreleasepool 進行 drain 操作時,所有標記了 autorelease 的對象的 retainCount 會被 -1滨溉。即 release 消息的發(fā)送被延遲到 pool 釋放的時候了弧岳。
在 ARC 環(huán)境下,蘋果引入了 @autoreleasepool 語法业踏,不再需要手動調(diào)用 autorelease 和 drain 等方法禽炬。
Autorelease Pool 的用處
在 ARC 下,我們并不需要手動調(diào)用 autorelease 有關(guān)的方法勤家,甚至可以完全不知道 autorelease 的存在腹尖,就可以正確管理好內(nèi)存。因為 Cocoa Touch 的 Runloop 中伐脖,每個 runloop circle 中系統(tǒng)都自動加入了 Autorelease Pool 的創(chuàng)建和釋放热幔。
當我們需要創(chuàng)建和銷毀大量的對象時,使用手動創(chuàng)建的 autoreleasepool 可以有效的避免內(nèi)存峰值的出現(xiàn)讼庇。因為如果不手動創(chuàng)建的話绎巨,外層系統(tǒng)創(chuàng)建的 pool 會在整個 runloop circle 結(jié)束之后才進行 drain,手動創(chuàng)建的話蠕啄,會在 block 結(jié)束之后就進行 drain 操作场勤。
例子
for (int i = 0; i < 100000000; i++)
{
@autoreleasepool
{
NSString* string = @"ab c";
NSArray* array = [string componentsSeparatedByString:string];
}
}
Autorelease Pool 進行 Drain 的時機
如上面所說,系統(tǒng)在 runloop 中創(chuàng)建的 autoreleaspool 會在 runloop 一個 event 結(jié)束時進行釋放操作歼跟。我們手動創(chuàng)建的 autoreleasepool 會在 block 執(zhí)行完成之后進行 drain 操作和媳。需要注意的是:
- 當 block 以異常(exception)結(jié)束時,pool 不會被 drain
- Pool 的 drain 操作會把所有標記為 autorelease 的對象的引用計數(shù)減一哈街,但是并不意味著這個對象一定會被釋放掉留瞳,我們可以在 autorelease pool 中手動 retain 對象,以延長它的生命周期(在 MRC 中)骚秦。
- Pool drain的時機(Runloop一次周期的休眠前)
Autorelease Pool 與函數(shù)返回值
如果一個函數(shù)的返回值是指向一個對象的指針她倘,那么這個對象肯定不能在函數(shù)返回之前進行 release柴信,這樣調(diào)用者在調(diào)用這個函數(shù)時得到的就是野指針了奈应,在函數(shù)返回之后也不能立刻就 release患雇,因為我們不知道調(diào)用者是
不是 retain 了這個對象个少,如果我們直接 release 了,可能導致后面在使用這個對象時它已經(jīng)成為 nil 了靶溜。
為了解決這個糾結(jié)的問題开瞭, Objective-C 中對對象指針的返回值進行了區(qū)分,一種叫做 retained return value罩息,另一種叫做 unretained return value嗤详。前者表示調(diào)用者擁有這個返回值,后者表示調(diào)用者不擁有這個返回值瓷炮,按照“誰擁有誰釋放”的原則葱色,對于前者調(diào)用者是要負責釋放的,對于后者就不需要了娘香。
- MRC
在 MRC 中我們需要關(guān)注這兩種函數(shù)返回類型的區(qū)別苍狰,否則可能會導致內(nèi)存泄露。
對于 retained return value烘绽,需要負責釋放
假設我們有一個 property 定義如下:
@property (nonatomic, retain) NSObject *property;
在對其賦值的時候淋昭,我們應該使用:
self.property = [[[NSObject alloc] init] autorelease];
然后在 dealloc 方法中加入:
[_property release];
_property = nil;
這樣內(nèi)存的情況大體是這樣的:
- init 把 retain count 增加到 1
- 賦值給 self.property ,把 retain count 增加到 2
- 當 runloop circle 結(jié)束時安接,autorelease pool 執(zhí)行 drain翔忽,把 retain count 減為 1
- 當整個對象執(zhí)行 dealloc 時, release 把 retain count 減為 0盏檐,對象被釋放
可以看到?jīng)]有內(nèi)存泄露發(fā)生歇式。
對于 unretained return value,不需要負責釋放
當我們調(diào)用非 alloc胡野,init 系的方法來初始化對象時(通常是工廠方法)材失,我們不需要負責變量的釋放,可以當成普通的臨時變量來使用:
NSString *name = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
self.name = name
// 不需要執(zhí)行 [name release]
- ARC
在 ARC 中我們完全不需要考慮這兩種返回值類型的區(qū)別硫豆,ARC 會自動加入必要的代碼龙巨,因此我們可以放心大膽地寫:
self.property = [[NSObject alloc] init];
self.name = [NSString stringWithFormat:@"%@ %@", firstName, lastName];