2013年6月4日 星期二

Objective-C Pointer

Objective-C中,呼叫方法時,參數的傳遞是 call by value,也就是複製一份值傳遞進去,網路上的有些資料提到類似 call by reference,其實骨子裡還是 call by value。而要解釋這個概念前不得不先解釋指標(Pointer)。

指標 Pointer

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSString * ptr = @"Content";
        NSLog(@"The value of ptr : %p",ptr);
        NSLog(@"The address of @\"Content\" : %p",@"Content");
        NSLog(@"The address of ptr : %p",&ptr);
    }
    return 0;
}

我們先來看這一行,NSString * ptr = @"Content"; ,這裡宣告了一個指標ptr,指向一個NSString的物件,而這個物件的值是@"Content",數值於電腦運算時,都存儲於記憶體中,可以想成一條馬路上有許多房子,大小固定,都有門牌,而物件有大有小,有些可能需要好幾間房子打通才放得下。示意圖如下,Ptr所存的值( 0x100001040 )剛好是這個字串的記憶體位置,ptr本身也有個Address是  0x7fff5fbff898。由這個例子可以知道要知道門牌位置必須使用 & 取址運算子,在NSLog中用%p列印出來。



那你也可能會問,ptr這個指標名稱又是存在哪裡了,應該也需要一塊記憶體去記錄吧!!沒錯,它在編譯執行後,作業系統記錄了它的名稱與Address,當你需要用ptr時,系統去找有沒有ptr這個東西,有的話又在記憶體的哪裡,這個你沒有辦法操作,也遠離了今天的主題,所以我們暫不討論。

&取址運算子,告訴我這個變數的記憶體位置。
*取值運算子,告訴我這個變數所指過去的物件值是什麼。

現在可以來看看方法參數的傳遞是如何運作的:

#import <Foundation/Foundation.h>

void someOperate(NSString *m);

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        NSString *o = @"Origin";
        NSLog(@"The address of o : %p",&o);
        NSLog(@"The value of o : %p",o);
        NSLog(@"The address of @\"Origin\" : %p",@"Origin");
        someOperate(o);
        NSLog(@"%@",o);
        NSLog(@"The address of o : %p",&o);
        NSLog(@"The value of 0 : %p",o);
    }
    return 0;
}

void someOperate(NSString *m)
{
    NSLog(@"**********");
    NSLog(@"The address of m : %p",&m);
    NSLog(@"The value of m :%p",m);
    m = @"Modify";
    NSLog(@"After");
    NSLog(@"The address of m : %p",&m);
    NSLog(@"The value of m :%p",m);
    NSLog(@"The address of @\"Modify\" : %p",@"Modify");
    NSLog(@"**********");
}



用一張圖來解釋發生什麼事情
當我們把o指標傳進方法後,複製了它的值給了m,而在方法中  m = @"Modify",又產生了一個物件NSString,m的值變成了@"Modify"的記憶體位置,所以不管我們在方法中怎麼改,原本的值不會有任何變化。

那如果我們想要的是直接更改原本的值,該怎麼做呢。有幾種方法,先講最容易的,把NSString改為NSMutableString,這樣在改值時,就會把原先的值,在原位置直接改變,而不是新產生一個新的物件。

#import <Foundation/Foundation.h> 

void someOperate(NSMutableString *m);

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        NSMutableString *o = [[NSMutableString alloc]initWithString:@"Origin"];
        NSLog(@"The address of o : %p",&o);
        NSLog(@"The value of o : %p",o);
        someOperate(o);
        NSLog(@"%@",o);
        NSLog(@"The address of o : %p",&o);
        NSLog(@"The value of 0 : %p",o);
    }
    return 0;
}

void someOperate(NSMutableString *m)
{
    NSLog(@"**********");
    NSLog(@"The address of m : %p",&m);
    NSLog(@"The value of m :%p",m);
    [m setString:@"Modify"];
    NSLog(@"After");
    NSLog(@"The address of m : %p",&m);
    NSLog(@"The value of m :%p",m);
    NSLog(@"**********");
}


但是如果沒有辦法更改為Mutable的類別時該怎麼辦,這時候有個迂迴的用法,指標的指標,既然無法更動指標所指的值,那就變動指標所存儲的記憶體Address,看一段代碼。

#import <Foundation/Foundation.h> 
void someOperate(NSString **m);

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        NSString *o = @"Origin";
        
        NSLog(@"The address of o : %p",&o);
        NSLog(@"The value of o : %p",o);
        NSLog(@"The address of @\"Origin\" : %p",@"Origin");
        
        someOperate(&o);
        
        NSLog(@"%@",o);
        NSLog(@"The address of o : %p",&o);
        NSLog(@"The value of o : %p",o);
    }
    return 0;
}

void someOperate(NSString **m)
{
    NSLog(@"**********");
    NSLog(@"The address of m : %p",&m);
    NSLog(@"The value of m :%p",m);
    NSLog(@"The value of *m : %p",*m);
    
    *m = @"Modify";
    
    NSLog(@"After");
    NSLog(@"The value of *m : %p",*m);
    NSLog(@"The address of m : %p",&m);
    NSLog(@"The value of m :%p",m);
    NSLog(@"The address of @\"Modify\" : %p",@"Modify");
    NSLog(@"**********");
}


這邊宣告一個變數o指向@"Origin",o的記憶體位置是f898,o的值是1048,也就是@"Origin"的記憶體位置,someOperate方法接受的參數為指標的指標,而也就是o指標的Address,傳入後,發現它複製了一份o指標,m所指的位置並不是o,接著把m所指向的值由1048改為1168 ( *m = @"Modify"),到目前為止都懂,奇妙的是原本o所存儲的值也跟著變了,藉由這個方式,可以在方法中修改原先傳入的指標的存儲值,使他指向新的物件。不過我疑惑的是,為什麼修改的是m所指向的值,o卻也跟著改變,這個我尚未明白,請高手指點明燈。

沒有留言: