首先聲明了兩個(gè)NSString屬性篷店,如下:
@property(nonatomic, strong) NSString *strongStr;
@property(nonatomic, copy) NSString *copyyStr;
下面我們分別以NSString和NSMutableString舉例
NSString場(chǎng)景一
// 使用NSString直接賦值
NSString *originStr = [NSString stringWithFormat:@"hello,originStr"];
_strongStr = originStr;
_copyyStr = originStr;
NSLog(@"originStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 對(duì)象地址: %p ,對(duì)象指針地址: %p ,對(duì)象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", _copyyStr, &_copyyStr, _copyyStr);
控制臺(tái)輸出
2018-09-06 09:06:31.741210+0800 PropertyDemo[2451:226039] originStr 對(duì)象地址: 0x60400025ff20 ,對(duì)象指針地址:0x7ffee80f9a48 ,對(duì)象的值:hello,originStr
2018-09-06 09:06:31.741346+0800 PropertyDemo[2451:226039] strongStr 對(duì)象地址: 0x60400025ff20 ,對(duì)象指針地址: 0x7f9561c12cc0 ,對(duì)象的值:hello,originStr
2018-09-06 09:06:31.741466+0800 PropertyDemo[2451:226039] copyyStr 對(duì)象地址: 0x60400025ff20 ,對(duì)象指針地址:0x7f9561c12cc8 ,對(duì)象的值:hello,originStr
結(jié)論:這種情況下矾屯,不管是用strong還是copy修飾的對(duì)象,其指向的地址都是originStr的地址枝誊。
NSString場(chǎng)景二
// 第二種場(chǎng)景:用NSString直接賦值
NSString *originStr = [NSString stringWithFormat:@"hello,originStr"];
self.strongStr = originStr;
self.copyyStr = originStr;
NSLog(@"originStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 對(duì)象地址: %p ,對(duì)象指針地址: %p ,對(duì)象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", _copyyStr, &_copyyStr, _copyyStr);
控制臺(tái)輸出
2018-09-06 09:33:35.203366+0800 PropertyDemo[2804:257010] originStr 對(duì)象地址: 0x604000443f00 ,對(duì)象指針地址:0x7ffee73bba28 ,對(duì)象的值:hello,originStr
2018-09-06 09:33:35.203533+0800 PropertyDemo[2804:257010] strongStr 對(duì)象地址: 0x604000443f00 ,對(duì)象指針地址: 0x7f8c40e09240 ,對(duì)象的值:hello,originStr
2018-09-06 09:33:35.203645+0800 PropertyDemo[2804:257010] copyyStr 對(duì)象地址: 0x604000443f00 ,對(duì)象指針地址:0x7f8c40e09248 ,對(duì)象的值:hello,originStr
為什么用了self.copyyStr = originStr進(jìn)行賦值宝恶,調(diào)用了setter方法之后烙丛,copyyStr指向的地址和originStr指向的地址還是相同的呢祭埂?
原因:這里的copy是淺拷貝面氓,并沒(méi)有生成新的對(duì)象
NSMutableString場(chǎng)景一
// 使用NSMutableString直接賦值
NSMutableString *originStr = [NSMutableString stringWithFormat:@"hello,originStr"];
_strongStr = originStr;
_copyyStr = originStr;
[originStr setString:@"hello,I changed"];
NSLog(@"originStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 對(duì)象地址: %p ,對(duì)象指針地址: %p ,對(duì)象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", _copyyStr, &_copyyStr, _copyyStr);
控制臺(tái)輸出
2018-09-06 09:17:25.259841+0800 PropertyDemo[2652:239951] originStr 對(duì)象地址: 0x600000259290 ,對(duì)象指針地址:0x7ffeeeb8aa28 ,對(duì)象的值:hello,I changed
2018-09-06 09:17:25.259991+0800 PropertyDemo[2652:239951] strongStr 對(duì)象地址: 0x600000259290 ,對(duì)象指針地址: 0x7f86bae0d3d0 ,對(duì)象的值:hello,I changed
2018-09-06 09:17:25.260125+0800 PropertyDemo[2652:239951] copyyStr 對(duì)象地址: 0x600000259290 ,對(duì)象指針地址:0x7f86bae0d3d8 ,對(duì)象的值:hello,I changed
結(jié)論:不論是用strong還是copy修飾的對(duì)象,其指針指向的地址依然還是originStr的地址蛆橡。
疑問(wèn):為什么_copyyStr的值會(huì)變成“hello,I changed”呢舌界?不應(yīng)該是“hello,originStr”嗎航罗?繼續(xù)往下看
NSMutableString場(chǎng)景二
// 第二種場(chǎng)景:用NSMutableString直接賦值
NSMutableString *originStr = [NSMutableString stringWithFormat:@"hello,originStr"];
self.strongStr = originStr;
self.copyyStr = originStr;
[originStr setString:@"hello,I changed"];
NSLog(@"originStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", originStr, &originStr, originStr);
NSLog(@"strongStr 對(duì)象地址: %p ,對(duì)象指針地址: %p ,對(duì)象的值:%@", _strongStr, &_strongStr, _strongStr);
NSLog(@"copyyStr 對(duì)象地址: %p ,對(duì)象指針地址:%p ,對(duì)象的值:%@", _copyyStr, &_copyyStr, _copyyStr);
控制臺(tái)輸出
2018-09-06 09:23:02.871307+0800 PropertyDemo[2707:246046] originStr 對(duì)象地址: 0x60000024f630 ,對(duì)象指針地址:0x7ffee00bca28 ,對(duì)象的值:hello,I changed
2018-09-06 09:23:02.871465+0800 PropertyDemo[2707:246046] strongStr 對(duì)象地址: 0x60000024f630 ,對(duì)象指針地址: 0x7fb9cf40ea90 ,對(duì)象的值:hello,I changed
2018-09-06 09:23:02.871570+0800 PropertyDemo[2707:246046] copyyStr 對(duì)象地址: 0x60000024fcc0 ,對(duì)象指針地址:0x7fb9cf40ea98 ,對(duì)象的值:hello,originStr
疑問(wèn):self.copyyStr值:hello,originStr 終于是我們想要的結(jié)果
_copyyStr = originStr;和self.copyyStr = originStr;有什么區(qū)別禀横?
原因:
用@property來(lái)聲明屬性變量時(shí),編譯器會(huì)自動(dòng)為我們生成一個(gè)以下劃線加屬性名命名的實(shí)例變量(@synthesize copyyStr = _copyyStr)粥血,并且生成其對(duì)應(yīng)的getter、setter方法酿箭。
1.self.copyyStr = originStr賦值時(shí)复亏,會(huì)調(diào)用coppyStr的setter方法
2._copyyStr = originStr 賦值時(shí)給_copyyStr實(shí)例變量直接賦值,并不會(huì)調(diào)用copyyStr的setter方法
而在setter方法中有一個(gè)非常關(guān)鍵的語(yǔ)句:
_copyyStr = [copyyStr copy];
結(jié)論:
使用self.copyyStr = originStr 賦值時(shí)缭嫡,調(diào)用copyyStr的setter方法缔御,setter方法對(duì)傳入的copyyStr做了次深拷貝生成了一個(gè)新的對(duì)象賦值給_copyyStr,所以_copyyStr指向的地址和對(duì)象值都不再和originStr相同妇蛀。
總結(jié)
由上面的例子可以得出:
1.當(dāng)原字符串是NSString時(shí)耕突,由于是不可變字符串,所以评架,不管使用strong還是copy修飾眷茁,都是指向原來(lái)的對(duì)象,copy操作只是做了一次淺拷貝纵诞。
2.當(dāng)源字符串是NSMutableString時(shí)上祈,strong只是將源字符串的引用計(jì)數(shù)加1,而copy則是對(duì)原字符串做了次深拷貝浙芙,從而生成了一個(gè)新的對(duì)象登刺,并且copy的對(duì)象指向這個(gè)新對(duì)象。
所以嗡呼,如果源字符串是NSMutableString的時(shí)候纸俭,使用strong只會(huì)增加引用計(jì)數(shù)。但是copy會(huì)執(zhí)行一次深拷貝南窗,會(huì)造成不必要的內(nèi)存浪費(fèi)揍很。而如果原字符串是NSString時(shí)廊宪,strong和copy效果一樣,就不會(huì)有這個(gè)問(wèn)題女轿。
但是箭启,我們一般聲明NSString時(shí),也不希望它改變蛉迹,所以一般情況下傅寡,建議使用copy,這樣可以避免NSMutableString帶來(lái)的錯(cuò)誤北救。
隨便補(bǔ)充幾個(gè)知識(shí)點(diǎn):
簡(jiǎn)要闡述內(nèi)存相關(guān)的關(guān)鍵字荐操?
Strong:
指向并持有該對(duì)象,引用計(jì)數(shù)會(huì)加1珍策。引用計(jì)數(shù)為0銷(xiāo)毀托启,可以通過(guò)將變量強(qiáng)制賦值 nil 來(lái)進(jìn)行銷(xiāo)毀。
Weak:
指向但是并不持有該對(duì)象攘宙,引用計(jì)數(shù)不會(huì)加1屯耸。在 Runtime 中對(duì)該屬性進(jìn)行了相關(guān)操作,無(wú)需處理蹭劈,可以自動(dòng)銷(xiāo)毀
assign:
assign主要用于修飾基本數(shù)據(jù)類(lèi)型疗绣,例如NSInteger,CGFloat铺韧,存儲(chǔ)在棧中多矮,內(nèi)存不用程序員管理
copy:
copy關(guān)鍵字和 strong類(lèi)似,copy 多用于修飾有可變類(lèi)型的不可變對(duì)象上 NSString,NSArray,NSDictionary上哈打。
__unsafe_unretain:
__unsafe_unretain 類(lèi)似于 weak 塔逃,但是當(dāng)對(duì)象被釋放后,指針依然保存著之前的地址料仗,被釋放后的地址變?yōu)?僵尸對(duì)象湾盗,訪問(wèn)被釋放的地址就會(huì)出問(wèn)題,所以說(shuō)他是不安全的罢维。
atomic:
這個(gè)屬性是為了保證在多線程的情況下淹仑,編譯器會(huì)自動(dòng)生成一些互斥加鎖的代碼,避免該變量的讀寫(xiě)不同步的問(wèn)題肺孵。
nonatomic :
如果該對(duì)象無(wú)需考慮多線程的情況匀借,這個(gè)屬性會(huì)讓編譯器少生成一些互斥代碼,可以提高效率平窘。
__weak 和 __unsafe_unretain 的區(qū)別吓肋?
__unsafe_unretain 在指向的內(nèi)存地址銷(xiāo)毀后,指針本身并不會(huì)自動(dòng)銷(xiāo)毀瑰艘,這也就造成了野指針是鬼,之后容易造成 Crash肤舞。__weak 在指向的內(nèi)存銷(xiāo)毀后,可以將指針變量置為 nil均蜜,這樣更加安全李剖。
__weak 修飾的變量在地址被釋放后,為何被置為 nil囤耳?
在 Runtime 中專(zhuān)門(mén)維護(hù)了一個(gè)用于存儲(chǔ) weak指針變量的 Hash 表篙顺。這個(gè)表 key 是 weak指針?biāo)赶虻膬?nèi)存地址,value 是指向這個(gè)內(nèi)存地址的所有 weak指針充择,實(shí)際上是一個(gè)數(shù)組德玫。釋放時(shí)根據(jù)對(duì)象地址獲取所有 weak指針 地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為 nil椎麦,最后把這個(gè) 對(duì)象 從weak表 中刪除宰僧。
使用atomic一定是線程安全的嗎?
atomic可以保證setter和getter存取的線程安全并不保證整個(gè)對(duì)象是線程安全的观挎。
比如琴儿,聲明一個(gè)NSMutableArray的原子屬性array,此時(shí)self.array和self.array = otherArray都是線程安全的键兜。但是凤类,使用[self.array objectAtIndex:index]就不是線程安全的,需要用鎖來(lái)保證線程安全性普气。