《編寫高質(zhì)量iOS與OS X代碼的52個有效方法》--第五章 第29條
(ps:此乃讀書筆記,加深記憶,僅供大家參考)
第5章 內(nèi)存管理
ARC幾乎把所有內(nèi)存管理事宜都交給編譯器決定,開發(fā)者只需專注于業(yè)務邏輯。
第29條:理解引用計數(shù)
Objective-C語言使用引用計數(shù)來管理內(nèi)存,也就是說,每個對象都有個可以遞增或遞減的計數(shù)器。如果想使某個對象繼續(xù)存活,那就遞增其引用計數(shù);用完了之后,就遞減其引用計數(shù)。計數(shù)變?yōu)?,就表示沒人關(guān)注此對象了,于是,就可以把它銷毀。
引用計數(shù)工作原理
在引用計數(shù)架構(gòu)下,對象有個計數(shù)器,用以表示當前有多少個事物想令此對象繼續(xù)存活下去。這在Objective-C中叫做“保留計數(shù)”(retain count),不過也可以叫“引用計數(shù)”(reference count)。NSObject協(xié)議聲明了下面三個方法用于操作計數(shù)器,以遞增或遞減其值:
- Retain 遞增保留計數(shù)。
- release 遞減保留計數(shù)。
- autorelease 待稍后清理“自動釋放池”(autorelease pool)時,再遞減保留計數(shù)。
查看保留計數(shù)的方法叫做retainCount, 此方法不太有用,即便在調(diào)試時也如此,所以筆者(蘋果公司)并不推薦大家使用這個方法。
對象創(chuàng)建出來時,其保留計數(shù)至少為1。 最終當保留計數(shù)歸零時,對象就回收了(deallocated),也就是說,系統(tǒng)會將其占用的內(nèi)存標記為“可重用”(reuse)。此時,所有指向該對象的引用也都變得無效了。
對象如果持有指向其他對象的強引用(strong reference),那么前者就“擁有”(own)后者。也就是說,對象想令其所引用的那些對象繼續(xù)存活,就可將其“保留”。等用完了之后,再釋放。
如果按“引用樹”回溯,那么最終會發(fā)現(xiàn)一個“跟對象”(root object)。在Mac OS X應用程序中,此對象就是NSApplication對象;而在iOS應用程序中,則是UIApplication對象。兩者都是應用程序啟動時創(chuàng)建的單例。
NSMutableArray * array = [[NSMutableArray alloc] init];
NSNumber * number = [[NSNumber alloc] initWithInt:2333];
[array addObject:number];
[number release];
//do something with 'array'
[array release];
在Objective-C中,調(diào)用alloc方法所返回的對象由調(diào)用者所擁有。也就是說,調(diào)用者已通過alloc方法表達了想令該對象繼續(xù)存活下去的意思。不過請注意,這并不是說此對象此時的保留計數(shù)必定是1。在alloc或“initWitInt:”方法的實現(xiàn)代碼中,也許還有其他對象也保留了此對象,所以,其保留計數(shù)至少為1。不能說保留計數(shù)一定是某個值,只能說你所執(zhí)行的操作是遞增了該計數(shù)還是遞減了該計數(shù)。
number對象調(diào)用release釋放之后,仍然存活.因為數(shù)組還在引用著它。然而絕不應該假設此對象一定存活,也就是說,不要像下面這樣編寫代碼:
NSNumber * number = [[NSNumber alloc] initWithInt:2333];
[array addObject:number];
[number release];
NSLog(@"number = %@", number);
如果調(diào)用release由于某些原因,其保留計數(shù)降至0,那么number對象所占內(nèi)存也許會回收,這樣的話,在調(diào)用NSLog可能就將是程序崩潰了。這里說“可能”,而沒說“一定”,因為對象所占的內(nèi)存在“解除分配”(deallocated)之后,只是放回“可用內(nèi)存池”(avaiable pool)。如果執(zhí)行NSLog時尚未復寫對象內(nèi)存,那么該對象仍然有效,這時程序不會崩潰。
為避免在不經(jīng)意間使用了無效對象,一般調(diào)用完release之后都會清空指針。這就能保證不會出現(xiàn)可能指向無效對象的指針,這種指針通常稱為“懸掛指針”(dangling pointer)。
NSNumber * number = [[NSNumber alloc] initWithInt:2333];
[array addObject:number];
[number release];
number = nil;
屬性存取方法中的內(nèi)存管理
若屬性為“strong關(guān)系”(strong relationship),則設置的屬性值會保留。
- (void)setFoo:(id)foo
{
[foo retain];
[_foo release];
_foo = foo;
}
此方法將保留新值并釋放舊值,然后更新實例變量,令其指向新值。順序很重要。
自動釋放池
調(diào)用release會立刻遞減對象的保留計數(shù)(而且還有可能令系統(tǒng)回收此對象),然而有時候可以不調(diào)用它,改為調(diào)用autorelease,此方法會在稍后遞減計數(shù),通常是在下一次“時間循環(huán)”(event loop)時遞減,不過也可能執(zhí)行的更早些。
此特性很有用,尤其是在方法中返回對象時更應該使用它。在這種情況下,我們并不總是想令方法調(diào)用者手工保留其值。
- (NSString *)stringValue
{
NSString * str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return self; //return [str autorelease];
}
此時返回的str對象其保留計數(shù)比期望值要多1(+1 retain count),因為調(diào)用alloc會令保留計數(shù)加1,而又沒有與之對應的釋放操作。
這里應該用autorelease,它會在稍后釋放對象,從而給調(diào)用者留下了足夠長的時間,使其可以在需要時先保留返回值。換句話說,此方法可以保證對象在跨越“方法調(diào)用邊界”(method call boundary)后一定存活。
保留環(huán)
通常采用“弱引用”(weak reference)來解決此問題,或是從外界命令循環(huán)中的某個對象不再保留另一個對象。這兩種方法都能打破保留環(huán),從而避免內(nèi)存泄露。
要點
- 引用計數(shù)機制通過可以遞增遞減的計數(shù)器來管理內(nèi)存。對象創(chuàng)建好之后,其保留計數(shù)至少為1。若保留計數(shù)為正,則對象繼續(xù)存活。當保留計數(shù)降為0時,對象就被銷毀了。
- 在對象生命期中,其余對象通過引用來保留或釋放此對象。保留與釋放操作分別會遞增或遞減保留計數(shù)。