Objective-C中的引用計數

來源:互聯網
上載者:User

標籤:

 導言

Objective-C語言使用引用計數來管理記憶體,也就是說,每個對象都有個可以遞增或遞減的計數器。如果想使某個對象繼續存活,那就遞增其引用計數;用完了之後,就遞減其計數。計數為0,就表示沒人關注此對象了,於是,就可以把它銷毀。

從Mac OS X 10.8開始,“垃圾收集器”(garbage collector)已經正式廢棄了,以Objective-C代碼編寫Mac OS X程式時不應再使用它,而iOS則從未支援過垃圾收集。因此,掌握引用計數機制對於學好Objective-C來說十分重要。Mac OS X程式已經不能再依賴垃圾收集器了,而iOS系統不支援此功能,將來也不會支援。

已經用過ARC的人可能會知道:所有與引用計數有關的方法都無法編譯,然而現在先暫時忘掉這件事。那些方法確實無法用在ARC中,不過本文就是要從Objective-C的角度講解引用計數,而ARC實際上也是一種引用計數機制,所以,還是要談談這些在開啟ARC功能時不能直接調用的方法。

工作原理

在引用計數架構下,對象有個計數器,用以表示當前有多少個事物想令此對象繼續存活下去。這在Objective-C中叫做“保留計數”(retain count),不過也可以叫“引用計數”(reference count)。NSObject協議聲明了下面三個方法用於操作計數器,以遞增或遞減其值:

1)retain 遞增保留計數。

2)release 遞減保留計數。

3)autorelease 待稍後清理“自動釋放池”(autorelease pool)時,再遞減保留計數。

 

是對象建立及保留計數操作的。

 

對象圖中,ObjectB與ObjectC都引用了ObjectA。若ObjectB與ObjectC都不再使用ObjectA,則其保留計數降為0,於是便可摧毀了。還有其他對象想令ObjectB與ObjectC繼續存活,而應用程式裡又有另外一些對象想令那些對象繼續存活。如果按“引用樹”回溯,那麼最終會發現一個“根對象”(root object)。在Mac OS X應用程式中,此對象是NSApplication對象;而在iOS應用程式中,則是UIApplication對象。兩者都是應用程式啟動時建立的單例。

下面這段代碼有助於理解這些方法的用法:

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

由於代碼中直接調用了release方法,所以在ARC下無法編譯。在Objective-C中,調用alloc方法所返回的對象由調用者所擁有。也就是說,調用者已通過alloc方法表達了想令該對象繼續存活下去的意願。不過,這並不是說對象此時的保留計數就是1。在alloc或“initWithInt:”方法的代碼實現中,也許還有其他對象也保留了此對象。絕不能說保留計數一定是某個值,只能說你所執行的操作的遞增了該計數還是遞減了該計數。

建立完數組後,把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可能就將使程式崩潰了。為什麼是“可能”,因為對象所佔的記憶體在“解除配置”(deallocated)之後,只是放回“可用記憶體池”(avaiable pool)。如果執行NSLog時還尚未覆寫對象記憶體,那麼該對象仍然有效,這是程式不會崩潰。故,因過早釋放對象而導致的bug很難調試

為避免在不經意間使用了無效對象,一般調用完release之後都會清null 指標。這就能保證不會出現可能指向無效對象的指標,這種指標通常稱為“懸掛指標”(dangling pointer)。例如,可以這樣編寫代碼來防止此情況發生:

NSNumber *number = [[NSNumber alloc] initWithInt:1337];[array addObject:number];[number release];number = nil;
屬性存取方法中的記憶體管理

如前所述,對象圖由相互關聯的對象所構成。剛才那個例子中的數組通過在其元素上調用retain方法來保留那些對象。不光數組,其他對象也可以保留別的對象,這一般通過訪問“屬性”來實現,而訪問屬性時,會用到相關執行個體變數的擷取方法和設定方法。若屬性為“strong關係”(strong relationship),則設定的屬性值會保留。比方說,有個名叫foo的屬性由名為_foo的執行個體變數所實現,那麼,該屬性的設定方法會是這樣:

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

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

自動釋放池

在Objective-C的引用計數架構中,自動釋放池是一項重要特性。調用release會立刻遞減對象的保留計數(而且還可能令系統回收此對象),然而有時候可以不調用它,改為調用autorelease,此方法會在稍後遞減計數,通常是在下一次“事件迴圈”(event loop)時遞減,不過也可能執行得更早些。 

此特性很有用,尤其是在方法中返回對象時更應該用它。在這種情況下,我們並總是想令方法調用者手工保留其值。比方說,有下面這個方法:

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

此時返回的str對象其保留計數比期望值要多1,因為調用者alloc會令保留計數加1,而又沒有與之對應的釋放操作。保留計數多1,就意味著調用者要負責處理多出來的這一次保留操作。必須設法將其抵消。這並不是說保留計數本身就一定是1,它可能大於1,不過那取決於“initWithFormat:”方法內的實現細節。你要考慮的是如何將多出來的這一次保留操作抵消掉。但是,不能在方法呢你釋放str,否則還沒等方法返回,系統就把該對象回收了。這裡應該用autorelease,它會在稍後釋放對象,從而給調用者留下了足夠長的時間,使其可以在需要時先保留傳回值。換句話說,此方法可以保證對象在跨越“方法調用邊界”(method call boundary)後一定存活。實際上,釋放操作會在清空最外層的自動釋放池時執行,除非你有自己的自動釋放池,否則這個時機指的就是當前線程的下一次事件迴圈。改寫stringValue方法,使用autorelease來釋放對象:

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

修改之後,stringValue方法把NSString對象返回給調用者,此對象必然存活。所以我們能夠如此使用它:

NSString *str = [self stringValue];NSLog(@"The string is: %@", str);

由於返回的str對象將於稍後自動釋放,所以多出來的那一次保留操作時自然就會抵消,無須再執行記憶體管理操作。因為自動釋放池中的釋放操作要等到下一次事件迴圈時才會執行,所以NSLog語句在使用str對象前不需要手工執行保留操作。但是,假如要持有此對象的話(比如將其設定給執行個體變數),那就需要保留,並於稍後釋放:

_instanceVariable = [[self stringValue] retain];//...[_instaceVariable release];

由此可見,autorelease能延長對象生命期,使其在跨越方法調用邊界後依然可以存活一段時間。

保留環

使用引用計數機制時,經常要注意的一個問題就是“保留環”(retain cycle),也就是呈環狀相互引用的多個對象。這將導致記憶體泄露,因為迴圈中的對象其保留計數不會降為0。對於迴圈中的每個對象來說,至少還有另外一個對象引用著它。

 

如,在這個迴圈裡,所以對象的保留計數都是1。在垃圾收集環境中,通常將這種情況認定為“孤島”(island of isolation)。此時,垃圾收集器會把三個對象全部回收。而在Objective-C的引用計數架構中,則享受不到這一便利。通常採用“弱引用”(weak reference)來解決此問題,或是從外界命令迴圈中的某個對象不再保留另外一個對象。這兩種辦法都能打破保留環,從而避免記憶體泄露。 

小結

引用計數機制通過可以遞增遞減的計數器來管理記憶體。對象建立好之後,其保留計數至少為1。若保留計數為正,則對象繼續存活。當保留計數降為0時,對象就被銷毀。

在對象生命週期中,其餘對象通過引用來保留或釋放此對象。保留與釋放操作分別會遞增及遞減保留計數。

 

Objective-C中的引用計數

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.