NSString的三種實現方式
OC這個語言在不停的升級自己的內存管理,盡量的讓自己的
OC的字符串
問題引入
在學習字符串的過程中間會遇到一個因為OC語言更新造成的問題
例如:
int main(int argc, const char * argv[]) {@autoreleasepool {NSString* str1 = [NSString stringWithFormat:@"hello"];NSString* str2 = [NSString stringWithFormat:@"hello"];NSString* str3 = @"hello";NSLog(@"%d", str1 == str2);NSLog(@"%d", str2 == str3);}return 0;
}
按照書上的老版本來說的話,我們這里應該輸出兩個0才對,因為一個第一個和第二個都是堆區創建的他們應該是不一樣的,但是結果卻出乎我們的意料,我們這里在堆區中創建的兩個字符串的指針地址是一樣的。結果輸出了1。下面我們就解決一下這個問題。
我們這時候輸出一下這幾個字符串的類型,這里就出現了這個NSTaggedPointerString這個類型,這時候我們會感到困惑,為什么我們明明是一個NSString類型,但是為什么會有這幾種情況呢?這里涉及到一個類簇的問題,但是這里就簡單理解成我們在這個NSString這個大類下有三種小的類別來管理這個類型,從而讓字符串有一個更好的儲存方式來保障我們的內存使用。
這時候,我們來正式介紹一下在OC中間有關字符串的幾種實現方式。
OC中字符串NSString
有三種實現方式,分別為
__NSCFConstantString,
__NSCFString,
NSTaggedPointerString.
現在我們通過一段代碼來分析這三種字符串的差異
代碼示例
我們給出一段代碼來分析一下有關這三種字符串的內容:
int main(int argc, const char * argv[]) {@autoreleasepool {NSString* str1 = @"hello";NSString* str2 = [NSString stringWithFormat:@"helloworld"];NSString* str3 = [NSString stringWithFormat:@"神"];NSString* str4 = [NSString stringWithFormat:@"hello"];NSLog(@"%lu, %@", [str1 retainCount], [str1 class]);NSLog(@"%lu, %@", [str2 retainCount], [str2 class]);NSLog(@"%lu, %@", [str3 retainCount], [str3 class]);NSLog(@"%lu, %@", [str4 retainCount], [str4 class]);//輸出他的一個引用計數和一個類型}return 0;
}
這是這個的輸出結果:
我們現在就了解一下這三種字符串
__NSCFConstantString類型
- 這個字符串類型是在常量區創建的一個字符串
- 這個字符串是一種編譯時的常量
- 我們可以用
@""
和stringWithString
方式創建 - 打印的
retainCount
的值是很大的,無法通過release方式去釋放,是一個單例模式
int main(int argc, const char * argv[]) {@autoreleasepool {NSString* str1 = @"hello";NSString* str2 = [NSString stringWithFormat:@"helloworld"];NSString* str3 = [NSString stringWithFormat:@"神"];NSString* str4 = [NSString stringWithFormat:@"hello"];NSLog(@"%lu, %@", [str1 retainCount], [str1 class]);//NSLog(@"%lu, %@", [str2 retainCount], [str2 class]);//NSLog(@"%lu, %@", [str3 retainCount], [str3 class]);//NSLog(@"%lu, %@", [str4 retainCount], [str4 class]);[str1 release];NSLog(@"%lu, %@", [str1 retainCount], [str1 class]);}return 0;
}
這里我們進行了一次release操作,按道理來說我們應該是把對他進行了一次釋放的,但是打印結果是
這里發現我們無法對其進行釋放,所以我們可以認為他是一個單例。
__NSCFString
- 這個字符串類型是一個在堆區創建的一個字符串
- 這個字符串是在運行的時候創建的
- 較長字符串會自動轉化成這個類型
- 中文字符串也會自動轉成這個類型
這是他的引用計數:
從引用計數可以看出他不是一個單例,可以被釋放。
借用一段學長的話來對于這個字符串類型進行一個講解:
即使兩個對象的內容相同,它們在堆上的內存地址也是不同的。每個對象都在獨立的內存空間中存儲,具有自己的地址。這意味著通過不同的對象引用訪問這兩個對象時,實際上訪問的是不同的內存地址。
NSTaggerPointerSring
這個字符串類型是最新的一個字符串類型,他也是在堆區創建的但是他可用用來存儲一下較短字符串,實現一個節約內存的效果,引用一段學長的話
TaggedPointer的意思是標簽指針,這是蘋果在 64 位環境下對 NSString,NSNumber
等對象做的一些優化。簡單來講可以理解為把指針指向的內容直接放在了指針變量的內存地址中,因為在 64 位環境下指針變量的大小達到了 8
位足以容納一些長度較小的內容。于是使用了標簽指針這種方式來優化數據的存儲方式。從他的引用計數可以看出,這貨也是一個釋放不掉的單例常量對象。在運行時根據實際情況創建。
這里是有關他的引用計數:
從這里可以看出他是一個單例。
也是通過一個stringWithFormat方式,但是由于較短英文字符串,所以可以用地址來直接存儲較短英文字符串的數值,這是一個單例。
- 是一個單例
- 是在堆區創建的一個字符串
- 可以將其當作一個偽對象,對象直接被存儲在指針的地址上面
- 較短且在堆區創建的字符串會呈現出這個樣式(長度小于9)
總結
我們這里簡單介紹了三種字符串的實現方式,這里只是簡單的介紹了這三種字符串的格子的一個特點,沒有深入的去研究它的底層代碼,我們這里主要需要理解在不同的情況下,字符串的創建出的類別是不一樣的。