obj-c本質就是"改進過的c語言",大家都知道c語言是沒有記憶體回收(GC)機制的(註:雖然obj-c2.0後來增加了GC功能,但是在iphone上不能用,因此對於iOS平台的程式員來講,這個幾乎沒啥用),所以在obj-c中寫程式時,對於資源的釋放得由開發人員手動處理,相對要費心一些。
引用計數
這是一種古老但有效記憶體管理方式。每個對象(特指:類的執行個體)內部都有一個retainCount的引用計數,對象剛被建立時,retainCount為1,可以手動調用retain方法使retainCount+1,同樣也可以手動調用release方法使retainCount-1,調用release方法時,如果retainCount值減到0,系統將自動調用對象的dealloc方法(類似於c#中的dispose方法),開發人員可以在dealloc中釋放或清理資源。
1、基本用法
為了示範這種基本方式,先定義一個類Sample
類介面部分Sample.h
//// Sample.h// MemoryManage_1//// Created by jimmy.yang on 11-2-19.// Copyright 2011 __MyCompanyName__. All rights reserved.//#import <Foundation/Foundation.h>@interface Sample : NSObject {}@end
類實現部分Sample.m
//// Sample.m// MemoryManage_1//// Created by jimmy.yang on 11-2-19.// Copyright 2011 __MyCompanyName__. All rights reserved.//#import "Sample.h"@implementation Sample-(id) init{if (self=[super init]){NSLog(@"建構函式被調用了!當前引用計數:%d",[self retainCount]);}return (self);}-(void) dealloc{NSLog(@"解構函式將要執行...,當前引用計數:%d",[self retainCount]);[super dealloc];}@end
代碼很簡單,除了"建構函式"跟"解構函式"之外,沒有任何其它多餘處理。
主程式調用
#import <Foundation/Foundation.h>#import "Sample.h"int main (int argc, const char * argv[]) {Sample *_sample = [Sample new];//建構函式被調用了!當前引用計數:1NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//1[_sample retain];NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//2[_sample retain];NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//3[_sample release];NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//2[_sample release];NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//1[_sample release];//解構函式將要執行...,當前引用計數:1NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//1,註:即便是在解構函式執行後,如果立即再次引用對象的retainCount,仍然返回1,但以後不管再試圖引用該對象的任何屬性或方法,都將報錯NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//對象被釋放之後,如果再嘗試引用該對象的任何其它方法,則報錯//[_sample retain];//同上,會報錯 return 0;}
這段代碼主要驗證:對象剛建立時retainCount是否為1,以及retain和release是否可以改變retainCount的值,同時retainCount減到0時,是否會自動執行dealloc函數
nil 的問題:
1.1 如果僅聲明一個Sample類型的變數(其實就是一個指標),而不執行個體化,其初始值為nil
1.2 變數執行個體化以後,就算release掉,dealloc被成功調用,其retainCount並不馬上回到0(還能立即調用一次且僅一次[xxx
retainCount]),而且指標變數本身也不會自動歸為nil值
1.3 dealloc被調用後,必須手動賦值nil,retainCount才會自動歸0
以上結論是實際實驗得出來的,見下面的代碼:
Sample *s ;NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is nil,retainCount=0s = [Sample new];NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is not nil,retainCount=1[s release];NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is not nil,retainCount=1//NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//報錯:Program received signal: “EXC_BAD_ACCESS”.s = nil;NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is nil,retainCount=0
所以千萬別用if
(x == nil) 或 if ([x retainCount]==0)來判斷對象是否被銷毀,除非你每次銷毀對象後,手動顯式將其賦值為nil
2、複雜情況
上面的樣本過於簡章,只有一個類自己獨耍,如果有多個類,且相互之間有聯絡時,情況要複雜一些。下面我們設計二個類Shoe和Man(即“鞋子類”和”人“),每個人都要穿鞋,所以Man與Shoe之間應該是Man擁有Shoe的關係。
Shoe.h介面定義部分
#import <Foundation/Foundation.h>@interface Shoe : NSObject {NSString* _shoeColor;int _shoeSize;}//鞋子尺寸-(void) setSize:(int) size;-(int) Size;//鞋子顏色-(void) setColor:(NSString*) color;-(NSString*) Color;//設定鞋子的顏色和尺碼-(void) setColorAndSize:(NSString*) pColor shoeSize:(int) pSize;@end
Shoe.m實現部分
//// Shoe.m// MemoryManage_1//// Created by jimmy.yang on 11-2-19.// Copyright 2011 __MyCompanyName__. All rights reserved.//#import "Shoe.h"@implementation Shoe//建構函式-(id)init{if (self=[super init]){_shoeColor = @"black";_shoeSize = 35;}NSLog(@"一雙 %@ %d碼 的鞋子造好了!",_shoeColor,_shoeSize);return (self);}-(void) setColor:(NSString *) newColor{_shoeColor = newColor;}-(NSString*) Color{return _shoeColor;}-(void) setSize:(int) newSize{_shoeSize = newSize;}-(int) Size{return _shoeSize;}-(void) setColorAndSize:(NSString *)color shoeSize:(int)size{[self setColor:color];[self setSize:size];}//解構函式-(void) dealloc{NSLog(@"%@ %d碼的鞋子正在被人道毀滅!",_shoeColor,_shoeSize);[super dealloc];}@end
Man.h定義部分
//// Man.h// MemoryManage_1//// Created by jimmy.yang on 11-2-20.// Copyright 2011 __MyCompanyName__. All rights reserved.//#import <Foundation/Foundation.h>#import "Shoe.h"@interface Man : NSObject {NSString *_name;Shoe *_shoe;}-(void) setName:(NSString*) name;-(NSString*)Name;-(void) wearShoe:(Shoe*) shoe;@end
Man.m實現部分
//// Man.m// MemoryManage_1//// Created by jimmy.yang on 11-2-20.// Copyright 2011 __MyCompanyName__. All rights reserved.//#import "Man.h"@implementation Man//建構函式-(id)init{if (self=[super init]){_name = @"no name";}NSLog(@"新人\"%@\"出生了!",_name);return (self);}-(void) setName:(NSString *)newName{_name =newName;}-(NSString*)Name{return _name;}-(void) wearShoe:(Shoe *)shoe{_shoe = shoe;}//解構函式-(void) dealloc{NSLog(@"\"%@\"要死了! ",_name);[super dealloc];}@end
main函數調用
#import <Foundation/Foundation.h>#import "Shoe.h"#import "Man.h"int main (int argc, const char * argv[]) {Man *jimmy = [Man new];[jimmy setName:@"Jimmy"];Shoe *black40 =[Shoe new];[black40 setColorAndSize:@"Black" shoeSize:40];[jimmy wearShoe:black40];[jimmy release];[black40 release];return 0; }
2011-02-23 13:05:50.550 MemoryManage[253:a0f] 新人"no name"出生了!
2011-02-23 13:05:50.560 MemoryManage[253:a0f] 一雙 black 35碼 的鞋子造好了!
2011-02-23 13:05:50.634 MemoryManage[253:a0f] "Jimmy"要死了!
2011-02-23 13:05:50.636 MemoryManage[253:a0f] Black 40碼的鞋子正在被人道毀滅!
以上是輸出結果,一切正常,jimmy與black40佔用的資源最終都得到了釋放。但是有一點不太合理,既然鞋子(black40)是屬於人(jimmy)的,為什麼人死了(即:[jimmy release]),卻還要main函數來責任燒掉他的鞋子?(即:main函數中還是單獨寫一行[black40 release]) 貌似人死的時候,就連帶自上的所有東西一併帶走,這樣更方便吧。
ok,我們來改造一下Man.m中的dealloc()方法,改成下面這樣:
//解構函式-(void) dealloc{NSLog(@"\"%@\"要死了! ",_name);[_shoe release];//這裡釋放_shoe[super dealloc];}
即:在Man被銷毀的時候,先把_shoe給銷毀。這樣在main()函數中,就不再需要單獨寫一行[black40 release]來釋放black40了.
現在又有新情況了:jimmy交了一個好朋友mike,二人成了鐵哥們,然後jimmy決定把自己的鞋子black40,跟mike共同擁有,於是main函數就成了下面這樣:
int main (int argc, const char * argv[]) {Man *jimmy = [Man new];[jimmy setName:@"Jimmy"];Shoe *black40 =[Shoe new];[black40 setColorAndSize:@"Black" shoeSize:40];[jimmy wearShoe:black40];Man *mike = [Man new];[mike setName:@"mike"];[mike wearShoe:black40];//mike跟jimmy,現在共同擁有一雙40碼黑色的鞋子[jimmy release];[mike release]; return 0;}
麻煩來了:jimmy在掛掉的時候(即[jimmy release]這一行),已經順手把自己的鞋子也給銷毀了(也許他忘記了mike也在穿它),然後mike在死的時候,準備燒掉自已的鞋子black40,卻被告之該對象已經不存在了。於是程式運行報錯:
Running…
2011-02-23 13:38:53.169 MemoryManage[374:a0f] 新人"no name"出生了!
2011-02-23 13:38:53.176 MemoryManage[374:a0f] 一雙 black 35碼 的鞋子造好了!
2011-02-23 13:38:53.177 MemoryManage[374:a0f] 新人"no name"出生了!
2011-02-23 13:38:53.179 MemoryManage[374:a0f] "Jimmy"要死了!
2011-02-23 13:38:53.181 MemoryManage[374:a0f] Black 40碼的鞋子正在被人道毀滅!
2011-02-23 13:38:53.183 MemoryManage[374:a0f] "mike"要死了!
Program
received signal: “EXC_BAD_ACCESS”.
sharedlibrary apply-load-rules all
(gdb)
上面紅色的部分表示程式出錯了:Bad_Access也就是說訪問不存在的地址。
最解決的辦法莫過於又回到原點,Man.m的dealloc中不連帶釋放Shoe執行個體,然後把共用的鞋子放到main函數中,等所有人都掛掉後,最後再銷毀Shoe執行個體,但是估計main()函數會有意見了:你們二個都死了,還要來麻煩我料理後事。
舉這個例子無非就是得出這樣一個原則:對於new出來的對象,使用retain造成的影響一定要運用相應的release抵消掉,反之亦然,否則,要麼對象不會被銷毀,要麼過早銷毀導致後面的非法引用而出錯。
下一回,我們來看看如何用自動釋放池來換一個方式來處理引用計數。