引用計數式記憶體管理的思考方式:
- 自己產生的對象,自己所持有。
- 非自己產生的對象,自己也可以持有。
- 不再需要自己持有的對象時釋放。
- 非自己持有的對象無法釋放。
對象操作 |
Objective-c方法 |
產生並持有對象 |
alloc/new/copy/mutableCopy方法 |
持有對象 |
retain方法 |
釋放對象 |
release方法 |
廢棄對象 |
dealloc方法 |
Cocoa 架構中 Foundation 架構中的NSObject 類擔負記憶體管理的職責。記憶體管理中的alloc/retain/release/dealloc方法分別指代NSObject類的alloc類方法,retain執行個體方法,
release執行個體方法和dealloc執行個體方法。
1、 自己產生的對象,自己所持有
使用下面名稱開頭的方法名意味著自己產生的對象只有自己持有。
- alloc
- new
- copy
- mutaleCopy
首先看前兩個: alloc new
//使用alloc類方法就能自己產生並持有對象,指向產生並持有對象的指標被賦給變數obj id obj = [[NSObject alloc] init]; //使用new類方法也可以產生並持有對象,所以 alloc 和 new 二者是完全一致的 id obj = [NSObject new];
糾正:上面代碼注釋中說到alloc 和new 二者是完全一致的,這種說法是不正確的!其實應該是:[NSObject new] 和 [[NSObject alloc] init] 這兩個語句的作用是一致的。
下面看一下後兩個 copy mutableCopy
這兩個的理解有點複雜。copy 方法利用 基於NSCopying 方法約定,由各類實現的 copyWithZone: 方法產生並持有對象的副本。
與copy方法類似,mutableCopy 方法利用基於 NSMutableCopying 方法約定,有各類實現的 mutableCopyWithZone: 方法產生並持有對象的副本。
兩者的區別在於:
copy 方法產生不可變更的對象,而mutableCopy方法產生可變更的對象。(這樣說可能有點抽象,下面用一個例子來說明!)
1. mutableCopy建立一個新的可變對象,並初始化為原對象的值,新對象的引用計數為 1;
2. copy 返回一個不可變對象。分兩種情況:
(1)若原對象是不可變對象,那麼返回原對象,並將其引用計數加 1;
(2)若原對象是可變對象,那麼建立一個新的不可變對象,並初始化為原對象的值,新對象的引用計數為 1。
//建立一個不可變數組 NSArray *immutableArray = [[NSArray alloc] initWithObjects:@"one",@"two",@"three", nil]; //從不可變數組複製而來一個可變數組 NSMutableArray *mutableArray = [immutableArray mutableCopy]; [mutableArray addObject:@"four"]; NSLog(@"count = %ld",(unsigned long)[mutableArray count]); //輸出: count = 4 NSMutableArray *array = [[NSMutableArray alloc] init]; array = [mutableArray copy]; //注意這裡使用copy複製得到的是一個不可變的對象,儘管使用的是NSMutableArray類型。所以下面一條語句想向數組中添加元素,雖然編譯成功,但是運行會出錯。 [array addObject:@"five"]; //運行出錯
進一步說,copy就是淺拷貝,mutableCopy就是深拷貝。
拷貝一個對象也就是建立一個新的執行個體,並且初始化為拷貝源的值。對於像boolean,integer這類值,拷貝就是直接賦值。對於指標形的object就分為淺拷貝和深拷貝。淺拷貝是只建立一個新的指標,並指向同一塊資料。深拷貝就是資料和指標都建立。
題外話:本來是想通過 retainCount 這個執行個體方法來觀察 引用計數值的,但是發現有一個問題:
NSObject* obj = [[NSObject alloc] init]; NSLog(@"%li",(unsigned long)[obj retainCount]); // print: 1 [obj release]; NSLog(@"%li",(unsigned long)[obj retainCount]); // print: 1
這段代碼兩次都是輸出 1 ;第一個輸出 1 ,應該沒有什麼問題,因為alloc 使得引用計數 +1 ;然後 release ,那麼引用計數值 -1 ,那麼就是應該輸出 0呀!這個是為什麼呢?
其實問題是這樣的:
首先我們先看看文檔中是如何介紹 retainCount 這個方法的:
Do not use this method. (required) |
|
This method is of no value in debugging memory management issues. Because any number of framework objects may have retained an object in order to hold references to it, while at the same time autorelease pools may be holding any number of deferred releases on an object, it is very unlikely that you can get useful information from this method. |
大概的意思是:這個方法在debug的過程中是沒有什麼價值的(大家就不要使用啦),因為系統會延遲釋放對象,所以不太可能通過這個方法得到有用的資訊。
所以呢?上面最後一個語句通過 retainCount輸出 引用計數值 得到的資訊是不準確的,其實已經釋放了對象,只不過系統延時釋放而已;也就說在最後一個輸出語句執行的時
候,obj 這個對象其實還沒有釋放(引用計數值 還不為 0),你想,要是等於 0,那系統不是直接廢棄對象了嗎?那麼你還怎麼能夠調用retainCount這個執行個體方法。
可以這樣測試一下,如果再加一個release語句的話,程式就會崩潰,要不 have a try ! 這個是我的理解。
2、非自己產生的對象,自己也可以持有
用 alloc new copy mutableCopy 以外的方法取得的對象,因為對象不是自己產生,所有對象不歸自己持有,但是可以取得對象的存在。
//取得對象的存在,但是自己並不持有對象 id obj = [NSMutableArray array]; //通過retain可以持有對象 [obj retain];
那麼通過retain方法,非自己產生的對象也可以自己持有,就像跟用 alloc new copy mutableCopy 方法產生對象並持有一樣。
3、不再需要自己持有的對象時釋放
自己持有的對象,一旦不需要,持有人有義務釋放該對象,使用release方法。
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 100)]; [label release]; //已經釋放的對象,不可以再訪問 label.text = @"hello world"; //運行時會崩潰
4、
如果想要用某個方法產生對象,並將其返還給該方法的調用方,讓調用方持有對象,如何?呢?
-(id)allocObject { id obj = [[NSObject alloc]init]; return obj;}
注意方法的命名規則,要以alloc new copy mutableCopy開頭。
那麼如果想通過某個方法取得對象的存在,而自己不持有對象,那麼該如何?呢?
-(id)object { id obj = [[NSObject alloc]init]; //自己持有對象 [obj autorelease]; //取得對象的存在,但是自己不持有對象 return obj;}
注意方法的命名規則,不要以alloc new copy mutableCopy開頭,因為不持有對象。
上面使用了autorelease方法,用該方法可以取得對象的存在,但自己不持有對象。autorelease提供這樣的功能,使對象在超出指定的生存範圍時能夠自動並正確的釋放(調用release方法)。
[obj autorelease]; 這個語句將obj對象註冊到 autoreleasepool (自動釋放緩衝池)中,在pool結束時自動調用release方法 。可以這樣理解,讓autoreleasepool 持有對象,讓其負責對象的釋放。
方法調用
id obj1 = [self allocObject]; //自己持有對象 id obj2 = [self object]; //自己不持有對象,只是取得對象存在 //當然也可以通過retain方法持有該對象 [obj2 retain];
5、非自己持有的對象無法釋放
對於用 alloc new copy mutableCopy 方法產生並持有的對象,或者是用 retain方法持有的對象,由於持有人都是自己,所以在不需要對象時,都要調用release 釋放對象,而除此之外所得到的對象絕對不能釋放,倘若在程式中釋放了不是自己持有的對象,那麼就會造成崩潰。
//例子 1 id obj1 = [self allocObject]; //自己持有對象 [obj1 release]; //對象已經釋放了 [obj1 release]; //釋放已經非自己持有的對象時,即訪問已經廢棄的對象,程式崩潰 //例子 2 id obj2 = [self object]; //自己不持有對象,只是取得對象存在 [obj2 release]; //釋放非自己持有的對象,程式崩潰
這裡的 allocObject 方法和 object 方法同上。
6、採用引用計數值進行記憶體管理的實現:
- 在objective - c 的對象中存有引用計數。
- 調用 retain 或者 alloc new copy mutableCopy 方法,引用計數值 +1;
- 調用release 後,引用計數值 -1;
- 引用計數值為0時,調用 dealloc 方法廢棄對象。
7、autorelease
autorelease 就是自動釋放,它很類似與C語言中的自動變數(局部變數)的特性。
c語言中自動變數在程式執行過程中,若超出範圍,那麼該自動變數被自動廢棄。那麼同理, autorelease 會像c語言中的自動變數那樣來對待執行個體對象。當其超出範圍時,對象執行個體的release方法會自動被調用。
使用方法如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //產生並持有NSAutoreleasePool對象 id obj = [[NSObject alloc] init]; [obj autorelease]; // 調用已經指派至的 autorelease [pool drain]; // 廢棄NSAutoreleasePool對象
最後一行的 [pool drain]; 等同於 [poolrelease];
這篇文章也是參看 《objective-c進階編程 ios與os x多線程和記憶體管理》這本書時摘抄記錄總結的。開始學習記憶體管理,這是第一篇記錄。