首先先看下面一個很簡單的 student class:
student.h
@interface Student: NSObject { NSString *iName; NSString *iStudentID; int age; } @property(nonatomic,copy) NSString *iName; @property(nonatomic,copy) NSString *iStudentID; @property(nonatomic,assign) int age; @end
student.m
#import "student.h" @implementation student @synthesize iName,iStudentID,age; - (void) dealloc { [iName release]; [iStudentID release]; [super dealloc]; } @end
這個實作類別方式非常的常見,像我這種懶人,不想要自己實作 setter 與 getter 的 method,就可以借助使用 @property 以及 @synthesize 後,編譯器會自動幫我們生成 getter 與 setter 的 method。但是,getter 與 setter 所實作的內容到底是什麼呢?
先看下面兩種對 iName 設值的方式:
iName = @"Tericky";//方式一 [self setIName : @"Tericky]";//方式二
方式一所使用的是直接設值,不經過setter,但是這種方式有時候會造成memory leak(下面會解釋)!
方式二所使用的是間接設值,呼叫由編譯器自動幫我們生成的 method 來將值設定給 iName 變數。
那麼 setter 內部實作的內容到底是什麼呢?其實是這樣的:
- (void) setIName : (NSString *) newIName { if(iName != newIName){ [iName release]; iName = [newIName retain]; } }
所以我們就可以知道,原來將值設給變數的時候,不能將值設好就算了,而是要將原本所設定的值的記憶體給釋放掉,才可以重新設值給變數,這就是剛剛方式一所說,可能會造成 memory leak 的原因!
可是這種使用方式會讓人搞混怎麼辦?怎麼知道它是用直接還是間接?所以像我這種懶人,就會將上面的例子改寫成這樣:
student.h
@interface Student: NSObject @property(nonatomic,copy) NSString *iName; @property(nonatomic,copy) NSString *iStudentID; @property(nonatomic,assign) int age; @end
student.m
#import "student.h" @implementation student @synthesize iName = _iName; @synthesize iStudentID = _iName; @synthesize age = _age; - (void) dealloc { [_iName release]; [_iStudentID release]; [super dealloc]; } @end
_iName = @"Tericky";//直接設值 => 呼叫順序:_iName => iName => iName = @"Tericky" NSLog(@"%@",_iName);//直接取值 self.iName = @"Tericky";//直接設值 NSLog(@"%@",self.iName);//直接取值 [self setIName : @"Tericky"];//間接設值 NSLog(@"%@",[self getIName]);//間接取值
接下來要談的是,function 的 return value 為什麼要 retain?先看一個例子:
- (NSString *) getResult { NSString *str = nil; // do something str = [[NSString alloc] initWithFormate : @"%d-%d", 10,20]; return str; // <= 這樣會有問題嗎? }
上面的例子會有問題嗎?答案是會!因為 str 沒有做 release 的動作,如果接收值的變數一樣沒有做 release ,就會造成 memory leak,但是我又不能寫成這樣:
- (NSString *) getResult { NSString *str = nil; // do something str = [[NSString alloc] initWithFormate : @"%d-%d", 10,20]; return str; [str release]; }
str 都已經 return 了,哪有辦法再做 release 的動作!所以我們要把 return 跟 release 的動作一起做,變成:
- (NSString *) getResult { NSString *str = nil; // do something str = [[NSString alloc] initWithFormate : @"%d-%d", 10,20]; return [str autorelease]; }
為什麼要設為 autorelease 而不是 release 呢?因為如果直接將它 release 的話,會在還沒有接收到值的時候,就釋放掉了,所以我們要用 autorelease 拖延一下釋放的時間。
那接收值的地方要怎麼處理呢?讓我們再看一個接收的例子:
- (NSString *) getResult { NSString *game = [self getResult]; //do something //經過好幾個運算之後 NSLog(@"%@",game); [game release]; }
這樣的結果,我保證會發生 crash !因為 getResult 的值早就被釋放掉了!這時候 retain 就派上用場了!
- (NSString *) getResult { NSString *game = [[self getResult] retain]; //do something //經過好幾個運算之後 NSLog(@"%@",game); [game release]; }讓我們來看看 retainCount 的變化:getResult.str.init(retainCount = 1) => return [str autorelease] (retainCount = 1) => game = [[self getResult] retain] (retainCount = 2) => autorelease 生效(retainCount = 1) => [game release] (retainCount = 0) => 回收。
這樣是不是就不會造成 crash ,也不會造成 memory leak 了!
上中下三篇的分享就到這邊,後續還有一些心得,等我想好主題之後,再做近一步的分享!
延伸閱讀:Objective-C 的記憶體回收機制(上)
延伸閱讀:Objective-C 的記憶體回收機制(中)
沒有留言:
張貼留言