2011年9月1日 星期四

Objective-C 的記憶體回收機制(下)

終於比較有空補完下集了!這一集主要想要分享Objective-C的直接存取與間接存取的差異,以及function return value 的 retain、release 時機。

首先先看下面一個很簡單的 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 的記憶體回收機制(中)

沒有留言: