本條要點:(作者總結)
objective-c 通過引用計數來管理記憶體(參見第29條)。每個物件都有乙個計數器,其值表明還有多少個其他物件想令此物件繼續存活。物件建立好之後,其保留計數大於 0。保留與釋放操作分別會使該計數遞增及遞減。當計數變為 0 時,物件就為系統所**並摧毀了。
nsobject 協議中定義了下列方法,用於查詢物件當前的保留計數:
- (nsuinteger)retaincount;
然而 arc 已經將此方法廢棄了。實際上,如果在 arc 中呼叫,編譯器就會報錯,這和在 arc 中呼叫 retain、release、autorelease 方法時的情況一樣。雖然此方法已經正式廢棄了,但還是經常有人誤解它,其實這個方法根本就不應該呼叫。若在不啟用 arc 的環境下程式設計(說真的,還是在 arc 下程式設計比較好),那麼仍可呼叫此方法,而編譯器不會報錯。所以,還是必須講清楚為何不應該使用此方法。
這個方法看上去似乎挺合理、挺有用的。它畢竟返回了保留計數,而此值對每個物件來說顯然都很重要。但問題在於,保留計數的絕對值一般都與開發者所應留意的事情完全無關。即便只在除錯時才能呼叫此方法,通常也還是無所助益的。
此方法之所以無用,其首要原因在於:它返回的保留計數只是某個給定時間點上的值。該方法並未考慮到系統會稍後把自動釋放池清空,因而不會將後續的釋放操作從返回值裡減去,這樣的話,此值就未必能真實反映實際的保留計數了。因此,下面這種寫法非常糟糕:
1while ([object
retaincount])
這種寫法的第乙個錯誤是: 它沒考慮到後續的自動釋放操作,只是不停地通過釋放操作來降低保留計數,直至物件為系統所**。假如此物件也在自動釋放池裡,那麼稍後系統清空池子時還要把它再釋放一次,而這將導致程式崩潰。
第二個錯誤在於:retaincount 可能永遠不返回0,因為有時候系統會優化物件的釋放行為,在保留計數還是1 的時候就把它**了。只有在系統不打算這麼優化時,計數值才會遞減至0。因此,保留計數可能永遠都不會完全歸零。所以說,這段**就算有時正常執行,也多半是憑運氣,而非理性判斷。物件**之後,如果 while 迴圈仍在執行,那麼目前的執行期系統一般都會直接令應用程式崩潰。
從來都不需要編寫這種**。這段**所要實現的操作,應該通過記憶體管理來解決。開發者在期望系統於某處**物件時,應該確保沒有尚未抵消的保留操作,也就是不要令保留計數大於期望值。在這種情況下,如果發現某物件的記憶體洩漏了,那麼應該檢查還有誰仍然保留這個物件,並查明其為何沒有釋放此物件。
讀者可能還是想看一看保留計數的具體值,然而看過之後就會覺得奇怪了;它的值為何這麼大呢?比方說,有下面這段**:
1 nsstring *string = @"some string";
23 nslog(@"
string retaincount = %lu
", [string
retaincount]);45
67 nsnumber *numberi = @1;8
9 nslog(@"
numberi retaincount = %lu
", [numberi reataincount]);
1011
1213 nsnumber *numberf = @3.141f;14
15 nslog(@"
numberf retaincount = %lu
", [numberf retaincount]);
在 64 位 mac os x 10.8.2 系統中,用 clang 4.1 編譯後,這段**輸出的訊息如下:
1string retaincount = 18446744073709551615
23 number retaincount = 9223372036854775807
45 number retainconnt = 1
第乙個物件的保留計數是 2的64次方減1,第二個物件的保留計數是 2的63次方減1。由於二者皆為 「單例物件」(singleton object),所以其保留計數都很大。系統會盡可能把 nsstring 實現成單例物件。如果字串像本例所舉的這樣,是個編譯期常量(compile-time constant),那麼就可以這樣來實現了。在這種情況下,編譯器會把 nsstring 物件所表示的資料放到應用程式的二進位制檔案裡,這樣的話,執行程式時就可以直接用了,無須再建立nsstring 物件。nsnumber 也類似,它使用了一種叫做 「標籤指標」(tagged pointer)的概念來標註特定型別的數值。這種做法不使用 nsnumber 物件,而是把與數值有關的全部訊息都放在指標裡面。執行期系統會在訊息派發期間檢測到這種標籤指標,並對它執行相應的操作,使其行為看上去和真正的 nsnubmer 物件一樣。這種優化只在某些場合使用,比如範例中浮點數物件就沒有優化,所以其保留計數就是 1。
另外,像剛才所說的那種單例物件,其保留計數絕對不會變。這種物件的保留及釋放操作都是「空操作」(no-op)。可以看到,即便兩個單例物件之間,其保留計數也各不相同,系統對其保留計數的這種處理方式再一次表明:我們不應該總是依賴保留計數的具體值來編碼。假如你根據nsnumber 物件的具體保留計數來增減其值,而系統卻以標籤指標來實現此物件,那麼編出來的**就錯了。
那麼,只為了除錯而使用 retaincount 方法行不行呢?即便只為除錯,此方法也不是很有用。由於物件可能處在自動釋放池中,所以其保留計數未必如想象般精確。而且其他程式庫也可能自行保留或釋放物件,這都會擾亂保留計數的具體取值。看了具體的計數值之後,你可能還誤以為是自己的**修改了它,殊不知其實是由深埋在另外乙個程式庫中的某段**所改的。以下列**為例:
1idobject =[self createobject];
23 [opaqueobject dosomethingwithobject:object];4
5 nslog(@"
retaincount = %lu
", [object retaincount]);
object 的保留計數是多少呢?這個計數可以是任意值。「dosomethingwithobject:」 方法也許會將物件加到多個 collection 中,而這些 collection 均會保留此物件。這個方法還可能會多次保留並自動釋放此物件,而其中某些自動釋放操作要留待系統稍後清空自動釋放池時才執行。因此,保留計數的實際值就不是那麼有用了。
那到底何時才應該用 retaincount 呢?最佳答案是:絕對不要用,尤其考慮到蘋果公司在引入 arc 之後已正式將其廢棄,就更不應該用了。
end
不要使用vector
做為乙個 stl容器,vector 有兩個問題 第一,它不是乙個真正 stl容器,第二,它並不儲存 bool 型別 除此以外,並沒有太多東西與本節題目有關 譯註,還不夠多嗎 乙個東西不能成為乙個stl容器,只因為會有人說它是乙個 譯註,乙個東西要成為stl容器,必須滿足所有 列於c 標準23.1節的...
盡量不要使用FindWindow
盡量不用 findwindow 最近發現 se6和 se5程序共存時視窗名稱一樣引起的 bug。原因是我們經常使用 findwindow 來獲得視窗控制代碼,然後進行訊息通訊,這樣呼叫簡單,但增加了不同模組之間的依賴性,比如同時有兩個程序時,就可能會找錯視窗。而如果靠人去維護這個視窗名稱,在程式工程...
盡量不要使用可變引數
在某些情況下我們希望函式引數的個數可以根據實際需要來確定,所以c語言中就提供了一種長度不確定的引數,形如 c 語言也繼承了這一語言特性。在採用ansi標準形式時,引數個數可變的函式的原型是 typefuncname typepara1,typepara2,這種形式至少需要乙個普通的形式引數,後面的省...