前文地址:《iOS基礎深入補完計劃》
在前文、我們提到了property中的關鍵字copy可以用來修飾不可變對象、以保護對象的封裝性。
那么、copy和strong修飾的屬性究竟有什么區(qū)別。為什么copy修飾之后、不受原變量的影響。
以下、將分析以下幾點:
- 引用計數(shù)
- 變量地址
- copy的具體實現(xiàn)
- 順帶分析兩個字符串的類型NSTaggedPointerString/NSCFConstantString
關于引用計數(shù)
先用mutable字符串進行測試:
-
strong修飾
NSMutableString * mOStr = [NSMutableString stringWithFormat:@"1234567890"]; printf("mOStr原始引用前計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); self.strongedStr = mOStr; printf("mOStr被strong引用后計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr)));結果
mOStr原始引用前計數(shù): 2 mOStr被strong引用后計數(shù): 3 _strongedStr計數(shù): 3
-
copy修飾
NSMutableString * mOStr = [NSMutableString stringWithFormat:@"1234567890"]; printf("mOStr原始引用前計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); self.copyedStr = mOStr; printf("mOStr被copy引用后計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr)));打?。?/p>
mOStr原始引用前計數(shù): 2 mOStr被copy引用后計數(shù): 2 _copyedStr計數(shù): 1
再用immutable字符串進行測試:
NSString * mOStr = [NSString stringWithFormat:@"1234567890"]; printf("mOStr原始引用前計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); self.strongedStr = mOStr; printf("_mOStr被strong引用后計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); printf("_strongedStr計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(_strongedStr))); self.copyedStr = mOStr; printf("_mOStr被copy引用后計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(mOStr))); printf("_copyedStr計數(shù): %ld\n",CFGetRetainCount((__bridge CFTypeRef)(_copyedStr)));打?。?/p>
mOStr原始引用前計數(shù): 2 _mOStr被strong引用后計數(shù): 3 _strongedStr計數(shù): 3 _mOStr被copy引用后計數(shù): 4 _copyedStr計數(shù): 4結論:
- 在mutable字符串下。使用strong修飾會增加原對象引用計數(shù)、使用copy修飾則不會。
- 在immutable下。使用strong/copy修飾都會增加原對象引用計數(shù)。
- 除了字符串以外、Array/Dictionary的測試結果相同。
至于為什么在immutable下、引用都會+1。有兩種可能。
- 像__block一樣、將原對象轉移到堆中并且將全部指針以及計數(shù)轉移到block內(nèi)的只針對想上。
- 單純對原對象增加了引用計數(shù)。
這兩點、可以從變量地址上鑒別。
關于變量地址
-
先用mutable字符串進行測試:
#define TLog(prefix,Obj) {NSLog(@"變量值地址:%p, 指向對象值:%@, 變量類型:%@--%@",Obj,Obj,[Obj class],prefix);} @interface ViewController () @property (nonatomic,strong,readwrite) NSString * strongedStr; @property (nonatomic,copy,readwrite) NSString * copyedStr; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSMutableString * mOStr = [NSMutableString stringWithFormat:@"1234567890"]; TLog(@"原對象mOStr", mOStr); self.strongedStr = mOStr; self.copyedStr = mOStr; TLog(@"改變前的原對象mOStr", mOStr); TLog(@"原對象改變前的strongedStr",_strongedStr); TLog(@"原對象改變前的copyedStr",_copyedStr); [mOStr appendString:@"lalala"]; TLog(@"改變后的原對象mOStr", mOStr); TLog(@"原對象改變后的strongedStr",_strongedStr); TLog(@"原對象改變后的copyedStr",_copyedStr); }打印:
變量值地址:0x604000247500, 指向對象值:1234567890, 變量類型:__NSCFString--原對象mOStr 變量值地址:0x604000247500, 指向對象值:1234567890, 變量類型:__NSCFString--改變前的原對象mOStr 變量值地址:0x604000247500, 指向對象值:1234567890, 變量類型:__NSCFString--原對象改變前的strongedStr 變量值地址:0x6040000346e0, 指向對象值:1234567890, 變量類型:__NSCFString--原對象改變前的copyedStr 變量值地址:0x604000247500, 指向對象值:1234567890lalala, 變量類型:__NSCFString--改變后的原對象mOStr 變量值地址:0x604000247500, 指向對象值:1234567890lalala, 變量類型:__NSCFString--原對象改變后的strongedStr 變量值地址:0x6040000346e0, 指向對象值:1234567890, 變量類型:__NSCFString--原對象改變后的copyedStr
-
再用immutable字符串進行測試:
打?。?/p>
變量值地址:0x60000022cd20, 指向對象值:1234567890, 變量類型:__NSCFString--原對象mOStr 變量值地址:0x60000022cd20, 指向對象值:1234567890, 變量類型:__NSCFString--改變前的原對象mOStr 變量值地址:0x60000022cd20, 指向對象值:1234567890, 變量類型:__NSCFString--原對象改變前的strongedStr 變量值地址:0x60000022cd20, 指向對象值:1234567890, 變量類型:__NSCFString--原對象改變前的copyedStr 變量值地址:0x106a75130, 指向對象值:lalala, 變量類型:__NSCFConstantString--改變后的原對象mOStr 變量值地址:0x60000022cd20, 指向對象值:1234567890, 變量類型:__NSCFString--原對象改變后的strongedStr 變量值地址:0x60000022cd20, 指向對象值:1234567890, 變量類型:__NSCFString--原對象改變后的copyedStr
結論:
- 在mutable字符串下。使用strong修飾引用指向原變量內(nèi)存地址
copy則會在新地址上copy出一個變量- 在immutable下。使用strong/copy修飾都不會生成新的變量、所以指針是指向了同一個地址并且將計數(shù)+1。
- 無論聲明的屬性可變與否、copy聲明讓你得到一份不可變的副本。
- 除了字符串以外、Array/Dictionary的測試結果相同。
copy的具體實現(xiàn)
網(wǎng)上都說是在set內(nèi)部實現(xiàn)了[xxx copy]動作。
自己試了試、提示未式襲擊案copyWithZone方法。確實是如網(wǎng)上所言。
WechatIMG213.jpeg
所以、copy屬性不支持普通變量
因為普通變量本身就不能被修改。也不能使用copy方法
一些題外話
在測試地址的過程中、又出現(xiàn)兩個問題。
- NSCFConstantString是個什么東西。
- NSTaggedPointerString又是個什么。(如果將str的內(nèi)容由1234567890縮短成1234。NSCFString就會變成NSTaggedPointerString類型)
NSCFConstantString
總而言之:
-
NSCFConstantString類型的出現(xiàn)、取決于你這個字符串的創(chuàng)建方式。具體點:
NSString * mOStr1 = @"1234"; NSString * mOStr2 = [NSString stringWithString:@"1234"]; NSString * mOStr3 = [NSString stringWithFormat:@"1234"];//這個是正常生成的、是個對象。 -
先說第1、2種方式聲明的字符串:
- 以上兩種方式生成的字符串、無論長短都是NSCFConstantString類型。
- 引用計數(shù)無限大。
- 在值相同的情況下可以直接用 ‘==’判定。
- 經(jīng)測試、值相同時、變量地址相同。都存在于棧內(nèi)存上。(應該就是個常量字符串吧)
-
然后、最后一種方式生成的字符串:
- 其他objc對象類似的、在堆上分配內(nèi)存。
- 初始引用計數(shù)為1。
更多的測試、可以參閱這一片大佬的博客《NSString特性分析學習》
NSTaggedPointerString
我們可以拋開后面的String、單純的來看TaggedPointer(標記指針)對象。因為除了NSString、NSNumber和NSDate也有相應的TaggedPointer對象。
簡單來說:
- TaggedPointer用于存儲所需字節(jié)較小的變量型對象。
- TaggedPointer用于將數(shù)據(jù)直接保存在指針地址本身中(指針的值不再單純是地址了,而是真正的值)、借此不需要生成對象、節(jié)省了內(nèi)存和效率。
- TaggedPointer只是一個披著對象皮的普通變量而已。
參考《深入理解Tagged Pointer》、《【譯】采用Tagged Pointer的字符串》
最后
本文主要是自己的學習與總結。如果文內(nèi)存在紕漏、萬望留言斧正。如果不吝賜教小弟更加感謝。
