《編寫高質(zhì)量iOS與OS X代碼的52個有效方法》--第五章 第29條
(ps:此乃讀書筆記,加深記憶,僅供大家參考)
第5章 內(nèi)存管理
ARC幾乎把所有內(nèi)存管理事宜都交給編譯器決定旧困,開發(fā)者只需專注于業(yè)務(wù)邏輯靶草。
第29條:理解引用計數(shù)
Objective-C語言使用引用計數(shù)來管理內(nèi)存,也就是說擂涛,每個對象都有個可以遞增或遞減的計數(shù)器。如果想使某個對象繼續(xù)存活聊记,那就遞增其引用計數(shù)撒妈;用完了之后,就遞減其引用計數(shù)排监。計數(shù)變?yōu)?狰右,就表示沒人關(guān)注此對象了,于是舆床,就可以把它銷毀棋蚌。
引用計數(shù)工作原理
在引用計數(shù)架構(gòu)下,對象有個計數(shù)器挨队,用以表示當(dāng)前有多少個事物想令此對象繼續(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。 最終當(dāng)保留計數(shù)歸零時,對象就回收了(deallocated)馒稍,也就是說皿哨,系統(tǒng)會將其占用的內(nèi)存標記為“可重用”(reuse)。此時纽谒,所有指向該對象的引用也都變得無效了证膨。
對象如果持有指向其他對象的強引用(strong reference),那么前者就“擁有”(own)后者鼓黔。也就是說央勒,對象想令其所引用的那些對象繼續(xù)存活,就可將其“保留”澳化。等用完了之后崔步,再釋放。
如果按“引用樹”回溯缎谷,那么最終會發(fā)現(xiàn)一個“跟對象”(root object)井濒。在Mac OS X應(yīng)用程序中,此對象就是NSApplication對象慎陵;而在iOS應(yīng)用程序中加酵,則是UIApplication對象磷斧。兩者都是應(yīng)用程序啟動時創(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ù)組還在引用著它。然而絕不應(yīng)該假設(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時尚未復(fù)寫對象內(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)用押,則設(shè)置的屬性值會保留肢簿。
- (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í)行的更早些。
此特性很有用夹纫,尤其是在方法中返回對象時更應(yīng)該使用它咽瓷。在這種情況下,我們并不總是想令方法調(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,而又沒有與之對應(yīng)的釋放操作月匣。
這里應(yīng)該用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ù)存活。當(dāng)保留計數(shù)降為0時剑按,對象就被銷毀了疾就。
- 在對象生命期中,其余對象通過引用來保留或釋放此對象艺蝴。保留與釋放操作分別會遞增或遞減保留計數(shù)猬腰。