49、在上文5(1)中提到:當(dāng)NSObject對象的retainCount減為0之后术辐,就不要再去打印它的retainCount了砚尽,有可能導(dǎo)致crash。
為了驗(yàn)證這個說法辉词,可以通過初始化一個對象并釋放它尉辑,然后多次打印這個對象的retainCount來測試。
測試結(jié)果有很多種情況较屿,取3種情況展示如下:
可以發(fā)現(xiàn):再對象的retainCount減為0之后隧魄,再去打印它的retainCount卓练,程序會隨機(jī)在某個打印處crash。
對象被釋放了還去訪問它導(dǎo)致crash這個很好理解购啄,但是為什么在[sth release]之后襟企,這個NSObject的retainCount還是1呢?這是因?yàn)楫?dāng)retainCount為1的時候狮含,再執(zhí)行release的話對象就該被釋放了顽悼,那么此時系統(tǒng)就會選擇直接將對象釋放掉(這樣retainCount自然也就沒有了),而不會多花費(fèi)一次寫內(nèi)存的操作去修改它的retainCount為0几迄。
所以在retainCount為1的時候執(zhí)行了release之后蔚龙,再去訪問它的retainCount的時候,就只有兩種結(jié)果:要么得到的retainCount為1映胁,要么直接因?yàn)閼覓熘羔槍?dǎo)致EXC_BAD_ACCESS木羹。
另外,可以注意到解孙,程序crash的位置是不固定的坑填,由此也可以猜測:系統(tǒng)釋放對象是異步執(zhí)行的。
50弛姜、在MRC環(huán)境下脐瑰,如果在一個有對象創(chuàng)建的循環(huán)外嵌套了@autoreleasepool塊,當(dāng)循環(huán)的次數(shù)多了廷臼,有可能會出現(xiàn)這些創(chuàng)建的對象直到@autoreleasepool塊執(zhí)行完才釋放的情況苍在,如下:
這時候如果將@autoreleasepool塊移動到for循環(huán)的內(nèi)部勺鸦,每一次for循環(huán)都創(chuàng)建一個@autoreleasepool塊灭忠,那么for循環(huán)內(nèi)創(chuàng)建的對象就會被及時地釋放了,如下圖:
在ARC環(huán)境下則不會存在這個問題傲茄,不管使不使用@autoreleasepool塊结啼,for循環(huán)內(nèi)創(chuàng)建的對象都會被及時地釋放:
51掠剑、關(guān)于NSString的retainCount問題:
(1)屈芜、NSString的retainCount并不總是正確的郊愧,在不同的構(gòu)造方法下,可能會得出不同的retainCount井佑。
這是因?yàn)榫幾g器有些情況下不會把NSString對象納入內(nèi)存管理的范疇属铁,在這種情況下編譯器會把NSString對象當(dāng)成字符串常量來處理,就不會有retainCount了躬翁。甚至根據(jù)編譯器的不同焦蘑,完全相同的代碼也有可能得出不同的retainCount。
這是完全視編譯器而定的盒发。
(2)例嘱、但是也可以測試一下狡逢,NSString幾個常用的構(gòu)造方法會怎樣影響retainCount。使用以下代碼來測試:
輸出結(jié)果如下:
(3)拼卵、可以看到:
①奢浑、使用直接賦值、init方法或者initWithString:方法腋腮,retainCount都是-1雀彼,說明這種情況下NSString對象就被當(dāng)做字符串常量來處理了。同時可以注意到直接賦值和使用initWithString:方法得到的NSString對象的地址是相同的即寡,進(jìn)一步說明它們被當(dāng)做同一個字符串常量處理了徊哑;
②、比較詭異的是initWithFormat:和stringWithFormat:這兩種構(gòu)造方法聪富,在使用漢字或者使用英文且字符較長的時候莺丑,編譯器會將它當(dāng)做正常對象來處理,會有retainCount善涨,當(dāng)英文字符較短時窒盐,編譯器會將它當(dāng)做字符串常量來處理;
(4)钢拧、在這里只是做一個測試蟹漓,其實(shí)關(guān)注retainCount是不符合內(nèi)存管理4個規(guī)則的管理思想的,進(jìn)行內(nèi)存管理的時候關(guān)注重點(diǎn)應(yīng)當(dāng)是持有與釋放對象源内,而不是去關(guān)注有多少指針持有了這個對象(retainCount)葡粒。