iOS strong和copy的區別

QiShare發表於2018-08-09

級別: ★☆☆☆☆
標籤:「iOS」「NSString」「strong和copy」
作者: MrLiuQ

在iOS開發中,幾乎每天都會遇到NSString屬性的宣告, 在ARC記憶體管理機制下, NSString屬性宣告有兩個關鍵字可以選擇:strong和copy; 那麼問題來了,什麼時候用copy,什麼時候用strong?

下面我寫一個小demo,希望大家能看懂,也還請路過的大神指教!

我在.h檔案中宣告瞭兩個NSString屬性,如下:

@property(nonatomic, strong) NSString *strongStr;

@property(nonatomic, copy) NSString *copyyStr;
// 注:不能以alloc,new,copy,mutableCopy 作為開頭命名,比如:copyStr
複製程式碼

第一種場景:用NSString直接賦值

// 第一種場景:用NSString直接賦值
NSString *originStr1 = [NSString stringWithFormat:@"hello,everyone"];

_strongStr = originStr1;
_copyyStr = originStr1;
    
NSLog(@"第一種場景:用NSString直接賦值");
NSLog(@"               物件地址         物件指標地址        物件的值   ");
NSLog(@"originStr: %p , %p , %@", originStr1, &originStr1, originStr1);
NSLog(@"strongStr: %p , %p , %@", _strongStr, &_strongStr, _strongStr);
NSLog(@" copyyStr: %p , %p , %@", _copyyStr, &_copyyStr, _copyyStr);
複製程式碼

然後我們執行一下,列印結果如下圖:

iOS strong和copy的區別

結論:這種情況下,不管是用strong還是copy修飾的物件,其指向的地址都是originStr的地址。


第二種場景:用NSMutableString直接賦值

// 第二種場景:用NSMutableString直接賦值
NSMutableString *originStr2 = [NSMutableString stringWithFormat:@"hello,everyone"];

_strongStr = originStr2;
_copyyStr = originStr2;

[originStr2 setString:@"hello,QiShare"];
    
NSLog(@"第二種場景:用NSMutableString直接賦值");
NSLog(@"               物件地址         物件指標地址        物件的值   ");
NSLog(@"originStr: %p , %p , %@", originStr2, &originStr2, originStr2);
NSLog(@"strongStr: %p , %p , %@", _strongStr, &_strongStr, _strongStr);
NSLog(@" copyyStr: %p , %p , %@", _copyyStr, &_copyyStr, _copyyStr);
複製程式碼

然後我們執行一下,列印結果如下圖:

iOS strong和copy的區別

看到這裡,同學們可能會有疑問,為什麼不論是用strong還是copy修飾的物件,其指標指向的地址依然還是originStr的地址?為什麼_copyyStr的值會變成“hello,QiShare”呢?不應該是“hello,everyone”嗎? 我們們先不解釋,賣個關子,我們接著往下看。


第三種場景:用NSMutableString點語法賦值

// 第三種場景:用NSMutableString點語法賦值
NSMutableString *originStr3 = [NSMutableString stringWithFormat:@"hello,everyone"];
    
self.strongStr = originStr3;
self.copyyStr = originStr3;
    
[originStr3 setString:@"hello,QiShare"];
    
NSLog(@"第三種場景:用NSMutableString點語法賦值");
NSLog(@"               物件地址         物件指標地址        物件的值   ");
NSLog(@"originStr: %p , %p , %@", originStr3, &originStr3, originStr3);
NSLog(@"strongStr: %p , %p , %@", _strongStr, &_strongStr, _strongStr);
NSLog(@" copyyStr: %p , %p , %@", _copyyStr, &_copyyStr, _copyyStr);
複製程式碼

然後我們執行一下,列印結果如下圖:

iOS strong和copy的區別

OK,這回我們終於看到我們希望看到的結果了, _copyyStr依然是“hello,everyone”,沒有變成“hello,QiShare”, _copyyStr指標指向的地址不再是_originStr的地址。 細心的同學會發現,第三種在賦值的時候用了點語法,而不是直接賦值。 除了將 _strongStr = originStr2; 改為 self.strongStr = originStr3; _copyyStr = originStr2;改為 self.copyyStr = originStr3; 其餘完全一樣。

也就是說,我們將_copyyStr = originStr2;改為 self.copyyStr = originStr3;才導致了_copyyStr的值在第三種情況下依然沒有改變,這是為什麼呢?

當我們用@property來宣告屬性變數時,編譯器會自動為我們生成一個以下劃線加屬性名命名的例項變數(@synthesize copyyStr = _copyyStr),並且生成其對應的getter、setter方法。 當我們用self.copyyStr = originStr賦值時,會呼叫coppyStr的setter方法,而_copyyStr = originStr 賦值時給_copyyStr例項變數直接賦值,並不會呼叫copyyStr的setter方法,而在setter方法中有一個非常關鍵的語句:

_copyyStr = [copyyStr copy];
複製程式碼

結論:第三種場景中用self.copyyStr = originStr 賦值時,呼叫copyyStr的setter方法,setter方法對傳入的copyyStr做了次深拷貝生成了一個新的物件賦值給_copyyStr,所以_copyyStr指向的地址和物件值都不再和originStr相同。


第四種場景:用NSString點語法賦值

// 第四種場景:用NSString點語法賦值
NSString *originStr4 = [NSString stringWithFormat:@"hello,everyone"];

self.strongStr = originStr4;
self.copyyStr = originStr4;
    
NSLog(@"第三種場景:用NSMutableString點語法賦值");
NSLog(@"               物件地址         物件指標地址        物件的值   ");
NSLog(@"originStr: %p , %p , %@", originStr4, &originStr4, originStr4);
NSLog(@"strongStr: %p , %p , %@", _strongStr, &_strongStr, _strongStr);
NSLog(@" copyyStr: %p , %p , %@", _copyyStr, &_copyyStr, _copyyStr);
複製程式碼

這裡我們將_copyyStr = originStr;改成了self.copyyStr = originStr; 這時候列印結果會是什麼樣呢?

iOS strong和copy的區別

看了列印結果,可能有的同學會產生疑問,為什麼用了self.copyyStr = originStr進行賦值,呼叫了setter方法,呼叫了_copyyStr = [copyyStr copy]之後,_copyyName指向的地址和originStr指向的地址還是相同的呢?

原因:這裡的copy是淺拷貝,並沒有生成新的物件

總結:

由上面的例子可以得出:

  • 當原字串是NSString時,由於是不可變字串,所以,不管使用strong還是copy修飾,都是指向原來的物件,copy操作只是做了一次淺拷貝。
  • 而當源字串是NSMutableString時,strong只是將源字串的引用計數加1,而copy則是對原字串做了次深拷貝,從而生成了一個新的物件,並且copy的物件指向這個新物件。另外需要注意的是,這個copy屬性物件的型別始終是NSString,而不是NSMutableString,如果想讓拷貝過來的物件是可變的,就要使用mutableCopy。 所以,如果源字串是NSMutableString的時候,使用strong只會增加引用計數。 但是copy會執行一次深拷貝,會造成不必要的記憶體浪費。而如果原字串是NSString時,strong和copy效果一樣,就不會有這個問題。

但是,我們一般宣告NSString時,也不希望它改變,所以一般情況下,建議使用copy,這樣可以避免NSMutableString帶來的錯誤。

順便路過提一下assign與weak

我們都知道,assign用來修飾基本資料型別,weak用來修飾OC物件。

其實照理,assign也能修飾OC物件,但是assign修飾的物件在該物件釋放後,其指標依然存在,不會被置為nil——這就造成了一個很嚴重的問題:出現了野指標。當訪問這個野指標時,指向了原地址,而原地址是nil,所以會造成程式的crash。但是用weak來修飾的話,物件釋放的時候會把指標置為nil,從而避免了野指標的出現。

那又有個疑問出現了,憑什麼基本資料型別就可以使用assign。這就要扯到堆和棧的問題了,基本資料型別會被分配到棧空間,而棧空間是由系統自動管理分配和釋放的,就不會造成野指標的問題。

ps:本文demo連結

關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)

推薦文章:iOS UISlider數值與滑動聯動

相關文章