級別: ★☆☆☆☆
標簽:「iOS」「NSString」「strong和copy」
作者: MrLiuQ
審校: QiShare團隊
在iOS開發(fā)中摸吠,幾乎每天都會遇到NSString
屬性的聲明,
在ARC內存管理機制下,
NSString屬性聲明有兩個關鍵字可以選擇:strong
和copy
饵沧;
那么問題來了,什么時候用strong
责球,什么時候用copy
滤馍?
下面我寫一個小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);
然后我們運行一下,打印結果如下圖:
結論:這種情況下蜈膨,不管是用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);
然后我們運行一下翁巍,打印結果如下圖:
看到這里驴一,同學們可能會有疑問,為什么不論是用
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);
然后我們運行一下渔隶,打印結果如下圖:
OK羔挡,這回我們終于看到我們希望看到的結果了,
_copyyStr依然是“hello间唉,everyone”绞灼,沒有變成“hello,QiShare”呈野,
_copyyStr指針指向的地址不再是_originStr的地址低矮。
細心的同學會發(fā)現(xiàn),第三種在賦值的時候用了點語法
被冒,而不是直接賦值军掂。
除了將 _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;
這時候打印結果會是什么樣呢佛析?
看了打印結果益老,可能有的同學會產生疑問,為什么用了self.copyyStr = originStr進行賦值寸莫,調用了setter方法捺萌,調用了_copyyStr = [copyyStr copy]之后,_copyyName指向的地址和originStr指向的地址還是相同的呢膘茎?
原因:這里的copy是淺拷貝桃纯,并沒有生成新的對象
總結:
由上面的例子可以得出:
- 當原字符串是
NSString
時酷誓,由于是不可變字符串,所以态坦,不管使用strong
還是copy
修飾盐数,都是指向原來的對象,copy
操作只是做了一次淺拷貝伞梯。 - 而當源字符串是
NSMutableString
時玫氢,strong
只是將源字符串的引用計數(shù)加1
,而copy
則是對原字符串做了次深拷貝
谜诫,從而生成了一個新的對象漾峡,并且copy的對象指向這個新對象。另外需要注意的是喻旷,這個copy屬性對象的類型始終是NSString
生逸,而不是NSMutableString,如果想讓拷貝過來的對象是可變的且预,就要使用mutableCopy
槽袄。
所以,如果源字符串是NSMutableString
的時候辣之,使用strong只會增加引用計數(shù)掰伸。
但是copy會執(zhí)行一次深拷貝,會造成不必要的內存浪費
怀估。而如果原字符串是NSString時狮鸭,strong和copy效果一樣,就不會有這個問題多搀。
但是歧蕉,我們一般聲明NSString時,也不希望它改變康铭,所以一般情況下惯退,建議使用copy
,這樣可以避免NSMutableString帶來的錯誤从藤。
順便路過提一下assign與weak
我們都知道催跪,assign
用來修飾基本數(shù)據類型,weak
用來修飾OC對象夷野。
其實照理懊蒸,assign
也能修飾OC對象
,但是assign
修飾的對象在該對象釋放后悯搔,其指針依然存在骑丸,不會被置為nil——這就造成了一個很嚴重的問題:出現(xiàn)了野指針
。當訪問這個野指針時,指向了原地址通危,而原地址有兩種情況:
- 第一種情況:原地址沒有改變铸豁,代碼運行通過,但很有可能有邏輯bug菊碟。
- 第二種情況:原地址已經改變节芥,結果不可預測,多數(shù)崩潰逆害,也有可能出現(xiàn)其他莫名錯誤藏古。
但是用weak
來修飾的話,對象釋放的時候會把指針置為nil
忍燥,從而避免了野指針
的出現(xiàn)。
那又有個疑問出現(xiàn)了隙姿,憑什么基本數(shù)據類型就可以使用assign
梅垄。這就要扯到堆
和棧
的問題了,基本數(shù)據類型會被分配到検溏瑁空間队丝,而棧空間是由系統(tǒng)自動管理分配和釋放
的欲鹏,就不會造成野指針的問題机久。
ps:本文demo鏈接:https://github.com/QiShare/QiStrongVsCopy
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)