Objective-C提供三種內(nèi)存管理模型:
- 自動垃圾回收
- 手動引用計數(shù)MRC和自動釋放池.
- 自動引用計數(shù)ARC.
Objective-C 2.0 是支持自動垃圾回收機制的.但是iOS運行環(huán)境并不支持自動垃圾回收.而且自OS X 10.8及以后也已經(jīng)不推薦使用了,而是建議使用ARC.
在iOS5之前使用的是手動引用計數(shù)簡稱MRC.iOS5蘋果推出了自動引用計數(shù)ARC,并且推薦大家使用自動引用計數(shù)進行內(nèi)存管理.自動引用計數(shù)ARC就是讓編譯器來進行內(nèi)存管理,編譯器會在合適的地方幫你插入retain或release,因此不再需要手工輸入retain和release代碼了.(編譯時)
引用計數(shù)式內(nèi)存管理的思想是:
- 自己生成的對象,自己持有. 對應 alloc/new/copy/mutableCopy方法.
- 非自己生成的對象,自己也能持有. 通過調(diào)用retain方法,就能使自己持有.
- 不需要自己持有對象時,需要釋放. 通過調(diào)用release方法,釋放自己持有的對象.
- 不能釋放非自己持有的對象.
在這些思想的指導下,Cocoa 建立了一套明確的內(nèi)存管理的方法命名規(guī)則特笋,在編寫用于對象生成或持有的方法時,必須要遵守這些命名規(guī)則.以 alloc/new/copy/mutableCopy開頭的方法名意味著自己生成并持有對象.在ARC有效時,還要加一條命名規(guī)則:以init開頭的方法.注意以init開頭的方法的規(guī)則要比 alloc/new/copy/mutableCopy更嚴格.該方法必須是實例方法并且必須要返回對象.返回的對象應為id類型或instanceType.該返回的對象并不注冊到自動釋放池里去.基本上只是對alloc方法返回的對象進行初始化處理并返回該對象.
使用上述方法之外的方法取得的對象是自己不持有的對象,但該對象存在.(根據(jù)第四條:不能釋放非自己持有的對象.此時如果是MRC環(huán)境,就不能調(diào)用release方法進行釋放,否則崩潰.這種情況在MRC時經(jīng)常發(fā)生.比如UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
從該方法的命名可以看出,取得的對象是自己不持有的,所以如果你對[btn release]將導致奔潰.還有一種情況就是過度release.比如UIButton *btn = [[UIButton alloc] init]; [btn release]; [btn release];
btn第一次release時,btn引用的對象就已經(jīng)被釋放了,btn這個指針變量已經(jīng)變成一個野指針了,如果再release,即是訪問這個野指針,這將導致崩潰.)
- (id)allocObject
{
id obj = [[NSSObject alloc] init];
return obj;
}
id obj1 = [obj0 allocObject]; //自己生成的對象自己持有
...
[obj1 release]; //所以這里使用完了,需要釋放.
以 alloc/new/copy/mutableCopy開頭的方法名意味著自己生成并持有對象.因此每次使用都需要時刻記住使用完后要釋放對象,但人往往會忘記釋放.如果某個方法能夠返回一個存在的對象,但該對象卻不需要我們自己去釋放,這將是一件多么讓人興奮的事!
那么如何返回一個存在但別人不持有的對象呢?
這就需要自動釋放池了.
自動釋放池( Autorelease Pool )提供了一個可以延遲給對象發(fā)送release消息的機制。它的使用場景比如從某方法返回一個對象時诉儒。給對象發(fā)送autorelease消息,那么該對象就被注冊到自動釋放池里面去了(這里需要注意的是如果給對象連續(xù)發(fā)送兩條autorelease消息,那么該對象會被注冊兩次,當池子drain時,該對象將被發(fā)送兩次release消息,這有可能導致過度release.),等到合適的時候自動釋放池會被發(fā)送drain消息,此時自動釋放池會給池子里面的每個對象都發(fā)送release消息,從而釋放掉對象.
- (id)object
{
id obj = [[NSObject alloc] init];
[obj autorelease];
return obj;
}
id obj1 = [obj0 object]; //取得的對象存在,但自己不持有該對象.所以使用完后,不需要release.
如果想持有,則需發(fā)送retain消息.
[obj1 retain];
//此時自己就持有了該對象,當不在需要持有時,你有義務釋放它.
...
[obj1 release];
dealloc是系統(tǒng)在對象被銷毀時自動調(diào)用的,不能手動調(diào)用!
如果重寫了dealloc方法,在MRC環(huán)境還需要調(diào)用[super dealloc]
(放在最后一句),但在ARC下禁止調(diào)用[super dealloc]
.
2.自動釋放池
自動釋放池( Autorelease Pool )提供了一個可以延遲給對象發(fā)送release消息的機制管怠。它的使用場景比如從某方法返回一個對象時淆衷。給對象發(fā)送autorelease消息,那么該對象就被注冊到自動釋放池里面去了,等到合適的時候自動釋放池會被發(fā)送drain消息,此時自動釋放池會給池子里面的每個對象都發(fā)送release消息,從而釋放掉對象.
這個"合適的時候"到底是什么時候呢?這就需要runloop了.iOS應用主線程的runloop(NSRunLoop)默認是開啟的.當事件發(fā)生時,runloop會處理事件,執(zhí)行我們寫的代碼.在處理事件之前,它會先創(chuàng)建好一個新的自動釋放池對象(NSAutoreleasePool對象),等到處理完這個事件時,runloop會銷毀這個NSAutoreleasePool對象,此時自動釋放池會給池子里面的每個對象都發(fā)送release消息,從而釋放掉對象.因此我們可以不用顯示創(chuàng)建一個自動釋放池.然而,在大量產(chǎn)生autorelease對象時,只要不廢棄NSAutoreleasePool對象,那么里面的對象就不能被釋放,這個時候就有可能產(chǎn)生內(nèi)存不足的情況.在這種情況下,有必要在適當?shù)牡胤缴?持有,廢棄NSAutoreleasePool對象.
MRC的寫法:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
ARC的寫法:
@autoreleasepool
{
id obj = [NSMutableArray array];
}//對象在這里被釋放,并銷毀.
注意:ARC對自動釋放在運行時做了一些優(yōu)化.一個對象原本應注冊到自動釋放池中,但是有些情況下經(jīng)ARC優(yōu)化后,這個對象就省略了自動釋放池的注冊.從而縮短了這個對象的生命周期.所以在ARC下,某個不以 alloc/new/copy/mutableCopy開頭的方法,返回的對象我們不應該假定它就是在自動釋放池中.
以下是Clang3.9文檔的說明
3.2.3 Unretained return values
A method or function which returns a retainable object type but does not return a retained value must ensure that the object is still valid across the return boundary.
When returning from such a function or method, ARC retains the value at the point of evaluation of the return statement, then leaves all local scopes, and then balances out the retain while ensuring that the value lives across the call boundary. In the worst case, this may involve an autorelease, but callers must not assume that the value is actually in the autorelease pool.
ARC performs no extra mandatory work on the caller side, although it may elect to do something to shorten the lifetime of the returned value.
舉個例子:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"--------");
for (NSInteger i = 0; i < 100000000; i++)
{
People *p = [People productPeople];
}
NSLog(@"+++++++");
}
這段代碼在MRC下將導致內(nèi)存的急劇增長,并導致應用被系統(tǒng)直接干掉.
說明了-productPeople方法產(chǎn)生的autorelease對象由于自動釋放池沒被銷毀前它里面的對象也不會被釋放而導致內(nèi)存爆漲.
而在ARC下,由于ARC對自動釋放池有做優(yōu)化,所以并沒有引起內(nèi)存的太大變化.