本文主要是實驗了兩個不同的NSString初始化方法導致的奇怪現(xiàn)象,因為能力有限,只進行了一番簡單的解釋,拋磚引玉。最后附上了一些猜想,供君參考。
1.當NSString長度小于10時,不再遵循引用計數(shù)規(guī)則
如果不能理解【引用計數(shù)規(guī)則】可以參考下另一篇文章iOS中copy,strong,retain,weak和assign的區(qū)別。
實驗代碼如下
NSString *stringLess10 = [[NSString alloc] initWithUTF8String:"123456789"];
NSString *string1Less10 = [[NSString alloc] initWithUTF8String:"123456789"];
NSLog(@"stringLess10值地址%p,引用計數(shù)%@",stringLess10,[stringLess10 valueForKey:@"retainCount"]);
NSLog(@"string1Less10值地址%p,引用計數(shù)%@",string1Less10,[string1Less10 valueForKey:@"retainCount"]);
NSString *stringMore10 = [[NSString alloc] initWithUTF8String:"12345678910"];
NSString *string1More10 = [[NSString alloc] initWithUTF8String:"12345678910"];
NSLog(@"stringMore10值地址%p,引用計數(shù)%@",stringMore10,[stringMore10 valueForKey:@"retainCount"]);
NSLog(@"string1More10值地址%p,引用計數(shù)%@",string1More10,[string1More10 valueForKey:@"retainCount"]);
輸出結果:
stringLess10值地址0xa1ea1f72bb30ab19,引用計數(shù)18446744073709551615
string1Less10值地址0xa1ea1f72bb30ab19,引用計數(shù)18446744073709551615
stringMore10值地址0x6080000306e0,引用計數(shù)1
string1More10值地址0x6080000306a0,引用計數(shù)1
現(xiàn)象:stringLess10和string1Less10值地址相同且引用計數(shù)異常,但在字符串長度大于10的stringMore10和string1More10上這個現(xiàn)象就不存在了
解釋:當NSString長度小于10時不再遵循引用計數(shù)規(guī)則,Tagged Pointer技術對其進行了優(yōu)化?;疽馑季褪悄J會將一些長度小于10的字符串直接保存在指針上面,下次創(chuàng)建相同值的時候直接用同一份拷貝,這樣既減少了一次指針到值的訪問,又減少了一份內(nèi)存的占用。
深入資料:http://www.cocoachina.com/ios/20150918/13449.html
2.NSString直接字符串賦值的異常
實驗代碼:
NSString *stringWithOutInit = @"1234567891011";
NSLog(@"stringWithOutInit,值地址%p,引用計數(shù)%@",stringWithOutInit,[stringWithOutInit valueForKey:@"retainCount"]);
輸出結果:
stringWithOutInit,值地址0x1081f7180,引用計數(shù)18446744073709551615
現(xiàn)象:值地址相當靠前,如一般是0xa1ea1f72bb30ab19,而他是0x1081f7180。另外引用計數(shù)非常大。
解釋:直接字符串賦值和init系列初始化方法有所不同。前者創(chuàng)建的是一個常量,不遵循引用計數(shù)。且在App結束前不會被釋放掉。引用計數(shù)在這個不能被釋放的內(nèi)存塊上默認返回是一個很大的值
我們通過weak類型來接收這個值,并把stringWithOutInit設置nil,驗證原來的內(nèi)存塊是否會釋放
實驗代碼
__weak NSString *weakStr = stringWithOutInit;
stringWithOutInit = nil;
NSLog(@"weakStr指針地址%p,值地址%p,引用計數(shù)%@,值為%@", &weakStr,weakStr,[weakStr valueForKey:@"retainCount"],weakStr);
輸出:
weakStr值地址0x1081f7180,值為1234567891011
發(fā)現(xiàn)值地址就是之前stringWithOutInit的值地址,且值依舊存在。
現(xiàn)象:雖然只有弱指針指向這個內(nèi)存塊,但依舊有值存在,未被釋放。
解釋:因為常量的引用計數(shù)無限大,自然值就不會被釋放
3.附加猜測NSString內(nèi)存中的存儲方式
我們知道NSMutableString對象的指針地址和值地址分別在棧和堆上,那么我們可以通過他的指針地址和棧地址來猜測nsstring不同方式初始化的時候指針和值地址在堆還是在棧上
試驗代碼:
NSMutableString *mstr = [[NSMutableString alloc] initWithFormat:@"%@",@"asdfasdfffffff"];
NSLog(@"mstr指針地址:%p 值地址%p,引用計數(shù)%@",&mstr,mstr,[mstr valueForKey:@"retainCount"]);
NSLog(@"stringLess10指針地址:%p 值地址%p,引用計數(shù)%@",&stringLess10,stringLess10,[stringLess10 valueForKey:@"retainCount"]);
NSLog(@"stringMore10指針地址:%p 值地址%p,引用計數(shù)%@",&stringMore10,stringMore10,[stringMore10 valueForKey:@"retainCount"]);
NSLog(@"stringWithOutInit指針地址:%p 值地址%p,引用計數(shù)%@",&stringWithOutInit,stringWithOutInit,[stringWithOutInit valueForKey:@"retainCount"]);
輸出結果:
mstr指針地址:0x7fff51dbbaa8 值地址0x60800006bc00,引用計數(shù)1
stringLess10指針地址:0x7fff51dbbaf8 值地址0xa1ea1f72bb30ab19,引用計數(shù)18446744073709551615
stringMore10指針地址:0x7fff51dbbae8 值地址0x600000024a80,引用計數(shù)1
stringWithOutInit指針地址:0x7fff51dbbad0 值地址0x0,引用計數(shù)(null)
現(xiàn)象和結論:
指針地址都是12位且值都相近,所以NSString不管初始化如何指針依舊保存在棧上面。
stringMore10和mstr值地址相近,所以init初始化方式在長度大于10的時候默認值存放在堆上。
stringLess值地址10長度為16位,完全不在堆上。
stringWithOutInit值地址沒有,說明他也不在堆上面,且和stringLess值存儲方式不一樣。
交流qq:578172874
錯誤之處還希望能幫忙提出來,一起學習,O(∩_∩)O謝謝了