1. 程式人生 > 其它 >Objective-C中的引用計數

Objective-C中的引用計數

技術標籤:記憶體管理iosobjective-c

什麼是引用計數?

Objective-C語言使用引用計數來管理記憶體,也就是說,每個物件都有個可以遞增或遞減的計數器。

引用計數如何工作?

如果想使某個物件繼續存活,那就遞增其引用計數;用完之後,就遞減其計數。計數變為0,就表示沒人關注此物件了,於是,就可以把它銷燬。

NSobject協議聲明瞭三個方法用於操作引用計數,以遞增或者遞減。

  • retain 遞增引用計數
  • release 遞減應用計數
  • autorelease 等到稍後清理”自動釋放池“的時候,再遞減引用計數。

物件建立之後,其保留計數至少為1。若想令其繼續存活,則呼叫retain方法。要是某部分程式碼不再使用此物件,不想令其繼續存活,那就呼叫release或autorelease方法。最終當引用計數歸零的時候,物件就回收了,系統會將其佔用的記憶體標記為”可重用“。此時,所有指向該物件的引用也都變得無效了。

容器類如何操作引用計數?

NSMutableArray *array = [[NAMutableArray alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
//do something with 'array'
[array release];

由於程式碼中直接使用了release方法,所以在ARC下無法編譯。在Objective-C中,呼叫alloc方法所返回的物件由呼叫者所擁有。

不過請注意,這並不是說物件此時的保留計數必定是1。在alloc或initWithInt:方法的實現程式碼中,也許還有其他物件也保留了此物件,所以,其引用計數可能會大於1。能夠肯定的是:引用計數至少為1。

引用計數的概念應該這樣理解,絕不應該說引用計數一定是某個值,只能說你所執行的操作是遞增了該計數還是遞減了該計數。

建立完陣列後,把number物件加入其中。呼叫陣列的addObject:方法是,陣列也會在number上呼叫retain方法,以期繼續保留此物件。這時,引用計數至少為2。接下來,程式碼不再需要number物件了,於是將其釋放。現在的引用計數至少為1。

這樣就不能正常使用number變量了,呼叫release之後,已經無法保證所指的物件仍然存活。當然,根據本例中的程式碼,我們顯然知道number物件在release之後仍然存活。然而絕不應該假設此物件一定存活,不能像下面這樣編寫程式碼:

NSNumber *number = [[NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
NSLog(@"number = %@", number);

如果呼叫release之後,基於某些原因,其引用計數降至0,那麼number物件所佔記憶體也許會回收,再呼叫NSLog可能就會使應用程式崩潰。

物件所佔的記憶體在解除分配之後,只是放回可用記憶體池,如果再次使用時未覆寫物件記憶體,那麼該物件仍然有效,這時應用程式不會崩潰。所以,因過早釋放物件而導致的bug很難除錯。

指向無效物件的指標通常被稱為”懸掛指標“,為避免出現這種情況,一般呼叫完release之後都會清空指標。

NSNumber *number = [[NSNumber alloc] initWithInt:1337];
[array addObject:number];
[number release];
number = nil;

屬性的存取方法中如何進行使用引用計數?

若屬性由名為_foo的例項變數所實現,那麼,該屬性的設定方法會是這樣:

- (void)setFoo:(id)foo 
{
   [foo retain];
   [_foo release];
   _foo = foo;
}

此方法將保留新值並釋放舊值,然後更新例項變數,令其指向新值。順序很重要。如果還未保留新值就先把舊值釋放了,而且兩個值又指向同一個物件,那麼,先執行的release操作就可能導致系統將此物件永久回收。而後續的retain操作則無法令這個已經徹底回收的物件復生,於是例項變數就變成懸掛指標了。

autorelease如何利用引用計數來延遲釋放記憶體?

呼叫release會立即遞減物件的引用計數,autorelease會在稍後遞減計數,通常是下一次的事件迴圈時才遞減,不過也可能會執行的更早一些。

比如,在方法中返回物件時,可以使用:

- (NSString *)stringValue
{
   NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
   return [str autorelease];
}

呼叫者在使用返回的NSString物件時,必然是存活的,並且稍後會自動釋放。所以我們只需要對我們稍後的操作作出的retain操作釋放就可以了。

autorelease能延長物件生命期,使其跨越方法呼叫邊界後依然可以存活一段時間。

錯誤使用引用計數,會帶來保留環

環狀相互應用的多個物件,會導致記憶體洩漏,因為迴圈中的物件其引用計數不會降為0。通常採用”弱引用“來解決此問題,或是從外界命令迴圈中的某個物件不再保留另一個物件。