NSTaggedPointerString,__NSCFConstantString,__NSCFString和NSString的關係?NSString為什麼用copy?各種型別的stringcopy
問題引入:
一.各型別字串的關係和儲存方式
NSString和NSMutableString相信我們平時都用過n遍了,但NSString真的都是一個儲存在堆區的物件嗎?如果在不同區,對它們進行copy操作,記憶體地址又會是什麼樣呢?
幾個需要注意的點:
記憶體地址由低到高分別為:程式區-->資料區-->堆區-->棧區
其中堆區分配記憶體從低往高分配,棧區分配記憶體從高往低分配
1.繼承關係:
NSTaggedPointerString(棧區) ---> NSString
__NSCFConstantString(資料常量區) ---> __NSCFString (堆區) --->NSMutableString --->NSString
2.對於NSStringFromClass()方法,字串較短的class,系統會對其進行比較特殊的記憶體管理,NSObject字串比較短,直接儲存在棧區,型別為NSTaggedPointerString,不論你NSStringFromClass多少次,得到的都是同一個記憶體地址的string;但對於較長的class,則為__NSCFString型別,而NSCFString儲存在堆區,每次NSStringFromClass都會得到不同記憶體地址的string
3.__NSCFConstantString型別的字串,儲存在資料區,即使當前控制器被dealloc釋放了,存在於這個控制器的該字串所在記憶體仍然不會被銷燬.通過快捷方式建立的字串,無論字串多長或多短,都是__NSCFConstantString型別,儲存在資料區.
下面是實際程式碼測試的結果,物件記憶體地址和指標記憶體地址都作了相應註釋:
NSString *str1 = @"abcdeefghijk";//該種方式無論字串多長,都是__NSCFConstantString型別,儲存在資料區,0x0000000104b2c400 NSString *strDigital = @"999";//即使很短,也是(__NSCFConstantString *)型別, 0x00000001092b1420,儲存在資料區 NSString *str1Copy = [str1 copy];//__NSCFConstantString,NSString型別的str1進行copy是淺拷貝,並不會拷貝一份新的記憶體地址,0x0000000104b2c400,記憶體地址和str1相同 NSMutableString *str1MutableCopy = [str1 mutableCopy];//__NSCFString,NSString型別str1進行mutableCopy後是深拷貝,0x0000600000d7d590 NSMutableString *str1MutaCopyCopy = [str1MutableCopy copy];//__NSCFString,mutableString型別的str1MutableCopy進行mutableCopy後是深拷貝,0x0000600000373980 [str1MutableCopy appendString:@"12"]; [str1MutaCopyCopy appendString:@"34"];//本行程式碼崩潰,原因是mutableString進行copy雖然是深拷貝,但返回的是不可變型別,只有NSMutableString才有appendString方法,__NSCFString沒有appendString方法;雖然用NSMutableString接收,但str1MutaCopyCopy的型別還是由實際執行過程決定 NSString * cfStringLongClass = NSStringFromClass([ViewController class]);//較長類情況下,該方法獲得的字串是型別__NSCFString儲存在堆區,0x00006000003739a0 NSString *taggedStrShortClass = NSStringFromClass([NSObject class]);//較短類情況下,該方法獲得的字串是NSTaggedPointerString型別,儲存在棧區,0x86b1d57ef6188519 NSString *taggedStrShortClassCopy = [taggedStrShortClass copy];//(NSTaggedPointerString *) $0 = 0xac71e37414f16209 @"NSObject" 記憶體地址不會變,淺拷貝,值也相同 NSString *taggedStrShortClassMutaCopy = [taggedStrShortClass mutableCopy];//(__NSCFString *) $2 = 0x00006000036f4030 @"NSObject",記憶體地址變化,深拷貝一份至堆區,值相同, NSObject *obj = [[NSObject alloc]init];//物件儲存在堆區,0x0000600000c61160 //總結:stringWithFormat方式,最終字串的型別由字串長度決定,少於10個字元型別為NSTaggedPointerString,否則為__NSCFString型別 NSString *strFormatLong = [NSString stringWithFormat:@"%@",@"01234567a909"];// ; 當長度超過9時,為__NSCFString,0x00006000003f6680,堆區 NSString *strFormatShort = [NSString stringWithFormat:@"%@",@"012345678"];//當字元長度位數為9或以下時,strFormat為NSTaggedPointerString,0xfc16f577dc1ba4f7,儲存在棧區 NSString *strShortConstantCopy = [@"999" copy];//__NSCFConstantString,0x00000001092b1420,資料區.NSString呼叫copy是淺拷貝,和strDigital記憶體地址相同 NSString *strShortConstantMutaCopy = [@"999" mutableCopy];//__NSCFString,0x0000600003fad410,堆區,NSMutable型別的string呼叫mutalbeCopy是深拷貝,返回一個b可變型別字串;呼叫copy也是深拷貝,返回不可變字串 //從以下兩個物件的記憶體地址可以得出結論:stringWithFormat不論接收什麼型別的字串引數,也無論它在資料區棧區還是堆區,都遵守長度臨界值9的規則 NSString *strShortConstantMutaCopyFormat = [NSString stringWithFormat:@"%@",strShortConstantCopy];//NSTaggedPointerString,0x8b25a274d8732690 NSString *strShortConstantCopyFormat = [NSString stringWithFormat:@"%@",strShortConstantMutaCopy];//NSTaggedPointerString,0x8b25a274d8732690
二.NSString為什麼用copy,copy修飾詞到底做了什麼工作?NSMutableString用copy修飾又如何?
測試主要注重下面幾方面:
1.NSString分別用copy和strong修飾
2.來源為可變字串和來源為不可變字串
3.修改字串之前和修改字串之後
注意:
1.不可變型別的字串是無法更改內容的,這裡說的修改其實是修改它的指標,讓它指向另一塊記憶體,所以不會影響self.string;
而可變字串是可以更改內容的,對於他的修改是修改這塊記憶體的內容.
2.copy修飾詞起作用是在setter方法中發生的,如果通過_string_copy = [NSMutableString new];設定,得到的self.string_copy仍然
是淺拷貝
3.一個物件的型別是由執行時決定的,和@property中的宣告無關,OC對於型別不匹配只會報警告,編譯可以通過,但並不代表你執行
時候就不會出錯.
4.NSString +copy ( 淺拷貝,返回不可變物件) NSString+mutableCopy(深拷貝,返回可變物件) NSMutableString+copy(深拷貝,返回不可變物件) NSMutableString+mutableCopy(深拷貝,返回可變物件)
依然先上程式碼,看執行結果,記憶體地址和相關注釋如下:
@property (nonatomic, copy) NSString *string_copy;
@property (nonatomic, strong) NSString *string_strong;
@property (nonatomic, copy) NSMutableString *string_muta_copy;
@property (nonatomic, strong) NSMutableString *string_muta_strong;
@property (nonatomic, strong) UIView *string_obj;
NSString *string = [NSString stringWithFormat:@"%@",@"1234567890"];//p/x string (__NSCFString *) $0 = 0x0000600003e31800 @"1234567890"
self.string_copy = string;// p/x _string_copy (__NSCFString *) $1 = 0x0000600003e31800 @"1234567890"
self.string_strong = string;//p/x _string_strong (__NSCFString *) $2 = 0x0000600003e31800 @"1234567890"
self.string_muta_copy = string;//(__NSCFString *) $1 = 0x0000600003e31800 @"1234567890"
self.string_muta_strong = string;//(__NSCFString *) $2 = 0x0000600003e31800 @"1234567890"
self.string_obj = string;//(__NSCFString *) $2 = 0x0000600003e31800 @"1234567890",型別是由具體執行過程決定的,即便用UIView接收它,也並沒有影響string_obj的型別
string = [NSString stringWithFormat:@"%@",@"abcdefghijklmn"];//p/x string (__NSCFString *) 0x0000600002d36dc0 @"abcdefghijklmn"
/*
一.來源是不可變字串(修改前):
a.當string的來源是非mutable型別時,不論是copy還是strong修飾,最終都只會僅僅拷貝一個指標,並不拷貝這塊值的記憶體,因為這塊記憶體儲存的字串
本來就是非mutable,即不可修改型別的,深拷貝一份
(lldb) p/x string
(__NSCFString *) $0 = 0x0000600002d36da0 @"1234567890"
(lldb) p/x _string_copy
(__NSCFString *) $1 = 0x0000600002d36da0 @"1234567890"
(lldb) p/x _string_strong
(__NSCFString *) $2 = 0x0000600002d36da0 @"1234567890"
一.來源是不可變字串(修改後):
--------> 此處開始修改源串string的值------>string = [NSString stringWithFormat:@"%@",@"abcdefghijklmn"];
(因為string本身是不可變的,所以:雖然說修改string的值,倒不如說實際上是修改string這個指標,讓string這個指標指向另一塊記憶體區域,
故結果是不會影響self.string_copy,self.string_strong所指向的那塊記憶體)
(lldb) p/x string
(__NSCFString *) $3 = 0x0000600002d36dc0 @"abcdefghijklmn"
(lldb) p/x _string_copy
(__NSCFString *) $4 = 0x0000600002d36da0 @"1234567890"
(lldb) p/x _string_strong
(__NSCFString *) $5 = 0x0000600002d36da0 @"1234567890"
(lldb)
*/
NSMutableString *strMuta = [[NSMutableString alloc]initWithString:@"一二三四"];//p/x &strMuta (NSMutableString **) $7 = 0x00007ffee4d919a0
self.string_strong = strMuta;// p/x &_string_strong (NSString **) $6 = 0x00007f8c5fc0d788
self.string_copy = strMuta;//p/x &_string_copy (NSString **) $8 = 0x00007f8c5fc0d780
self.string_muta_strong = strMuta;//(__NSCFString *) $0 = 0x000060000032d500
self.string_muta_copy = strMuta;//(__NSCFString *) $2 = 0x0000600000d264e0
[self.string_muta_copy appendString:@"1234"];//崩潰,原因是:copy修飾的string_muta_copy在接收到字串時,無論是可變還是不可變,都給你深拷貝一遍,NSMutableString呼叫copy方法雖是深拷貝,但返回的是不可變物件,不可變物件是沒有appendString方法的
/*
二:來源是可變字串(修改前)
0.未修改源muta字串時,可以看出:copy修飾的string_copy拷貝了一份記憶體,即值和strMuta一樣,但實際儲存這個字串值的記憶體不是同一塊.
而strong修飾的string_strong只拷貝了一份指標,並沒有拷貝儲存這塊值的記憶體,實際上只是指標不是同一個,指向的記憶體還是同一塊
(lldb) p/x strMuta
(__NSCFString *) $0 = 0x000060000318ab20 @"一二三四"
(lldb) p/x _string_copy
(__NSCFString *) $1 = 0x0000600003f88120 @"一二三四"
(lldb) p/x _string_strong
(__NSCFString *) $2 = 0x000060000318ab20 @"一二三四"
*/
[strMuta appendString:@"1234"];
/*
二.來源是可變字串(修改後)
1.修改源字串strMuta後,由於string_copy指標指向新拷貝的那一塊記憶體區域-->0x0000600003f88120,所以修改源串strMuta的那
塊記憶體-->0x000060000318ab20不會影響copy修飾的字串;而string_strong只是拷貝了一個指標,這個指標仍然指向-->0x000060000318ab20這
塊區域,和源串是同一塊記憶體,唯一不同的就是指標本身地址不同,而指標的內容是相同的,都儲存著源串的記憶體地址,修改源串導致string_strong儲存的值也
隨之變化
lldb) p/x strMuta
(__NSCFString *) $3 = 0x000060000318ab20 @"一二三四1234"
(lldb) p/x _string_copy
(__NSCFString *) $4 = 0x0000600003f88120 @"一二三四"
(lldb) p/x _string_strong
(__NSCFString *) $5 = 0x000060000318ab20 @"一二三四1234"
*/
從上面程式碼執行的結果分析,不難得出:其實copy就是在setter方法裡起了特殊作用,利用copy修飾的string,在setter方法中進行一次判斷,a.如果來源是可變字串,就深拷貝一份(為什麼深拷貝?因為來源是可變字串,這串字元就可能會被修改,如果不深拷貝一份的話,將來源串不小心被修改了,你的self.string也會跟著變化,如果發生了不可預見的結果,你肯定要怪xcode了,這個結果是你不希望發生的);b.如果來源是不可變字串,就直接賦值,不拷貝內容(為什麼只拷貝指標不拷貝內容?因為來源為不可變,你就算改變self.string也只能通過重新賦值來改變,賦值肯定是你自己操作的,如果因為重新賦值發生了不可預見的問題,那就是你自己的責任了)
從結論中我們可以推測NSMutableString不用copy修飾的原因了:如果你用copy去修飾一個NSMutableString的字串屬性string_muta_copy,如果將來你把一個不可變來源通過set方法賦值給這個屬性,set方法內部會因為這個來源是不可變的進行一次copy,copy後再將新的物件賦給_string_muta_copy,這時候的拷貝是深拷貝,但返回的是不可變物件,而在你宣告中你認為這個物件是可變的,編譯時期也會被認為是可變的,你向他傳送NSMutableString的方法訊息不會報錯,但到了執行時候回因為string_muta_copy是個不可變字串而找不到方法.
對於NSMutableString型別的屬性,我們在對它賦值時,最好確保是可變型別,防止以後你把它當做可變字串,進行拼接操作,而實際上它卻被你賦值了不可變字串而出錯
至此:大概講述了NSString和NSMutableString呼叫copy和mutableCopy.修飾詞用copy和strong的原因.
文章如有不對的地方還請儘快與我聯絡.我會盡快做出修改,以免錯誤誤導到別人.QQ:422538722