1.趣味思考
ARC機(jī)制下對(duì)象指針有三種修飾符,分別是__ strong、__ autoreleasing、__ weak。__ autoreleasing大家見(jiàn)的較少,我在這里大概說(shuō)明關(guān)鍵字的用法,使用具體將會(huì)在后面講到。
__autoreleasing關(guān)鍵字修飾的對(duì)象指針,編譯器將會(huì)通過(guò)objc_retainAutoreleasedReturnValue(id obj)方法進(jìn)行‘增加對(duì)象計(jì)數(shù)和調(diào)用autorelease將指針值放入autorelease pool中’這兩個(gè)操作。__weak修飾符修飾的對(duì)象指針是弱持有,不會(huì)導(dǎo)致引用計(jì)數(shù)的變化,在對(duì)象被dealloc時(shí)指針被置為nil,可以避免循環(huán)引用。__strong修飾符修飾的對(duì)象指針是強(qiáng)持有,會(huì)導(dǎo)致引用計(jì)數(shù)的變化,持有時(shí)retainCount+1,放棄持有時(shí)retainCount-1。
分享之前我們先進(jìn)行趣味思考,看一段代碼,預(yù)測(cè)輸出
其中的_objc_autoreleasePoolPrint();是NSObject.mm中的公開(kāi)方法,通過(guò)調(diào)用autorelease pool提供的printAll()方法打印所有的自動(dòng)釋放池內(nèi)容。autorelease pool邏輯上是大小可變的棧式數(shù)據(jù)結(jié)構(gòu),儲(chǔ)存對(duì)象指針,如圖

物理實(shí)現(xiàn)上是通過(guò)4KB頁(yè)表加雙鏈表鏈接實(shí)現(xiàn)autorelease pool,如下圖。物理實(shí)現(xiàn)在圖中一目了然,犧牲部分空間儲(chǔ)存連接的雙向指針及一些特征值。滿(mǎn)了就申請(qǐng)一頁(yè)內(nèi)存擴(kuò)展??臻g,供繼續(xù)壓棧使用;當(dāng)一個(gè)autorelease pool drain時(shí),彈出該pool所有對(duì)象指針(POOL_SENTINEL指示一個(gè)pool的起始位置,作為彈棧結(jié)束指示)。當(dāng)然autorelease pool中考慮了如果彈棧后hot頁(yè)(即壓棧會(huì)用到的頁(yè))占用超過(guò)一半,原來(lái)增長(zhǎng)的??臻g不會(huì)全部release,而是會(huì)預(yù)留一個(gè)頁(yè)以免即將遇到的再次申請(qǐng)內(nèi)存狀況帶來(lái)的無(wú)謂消耗。


代碼如下
#import <Foundation/Foundation.h>
extern void _objc_autoreleasePoolPrint(void);
NSMutableString * test(){
return [[NSMutableString alloc] initWithFormat:@"hello every body"];
}
int main(int argc, const char * argv[]) {
NSMutableString * st = test();
_objc_autoreleasePoolPrint();
NSMutableString * __weak stWeak = st; //塊外__weak
_objc_autoreleasePoolPrint();
@autoreleasepool{
NSMutableString * __weak stInWeak = st; //塊內(nèi)__weak
_objc_autoreleasePoolPrint();
NSMutableString * __autoreleasing stInAuto = st; //塊內(nèi)__autoreleasing
_objc_autoreleasePoolPrint();
NSMutableString * __strong stInStrong = st; //塊內(nèi)__strong
_objc_autoreleasePoolPrint();
NSMutableString * stNoModif = st; //塊內(nèi)無(wú)修飾符
_objc_autoreleasePoolPrint();
NSLog(@"%@",st);
NSLog(@"%@",stWeak);
NSLog(@"%@",stInWeak);
NSLog(@"%@",stInAuto);
NSLog(@"%@",stInStrong);
NSLog(@"%@",stNoModif);
_objc_autoreleasePoolPrint();
}
_objc_autoreleasePoolPrint();
[[NSRunLoop currentRunLoop] run];
return 0;
}
大家可以思考一下,每次打印autorelease pool出來(lái)的對(duì)象指針數(shù)量及原因,(5-10min)
答案如下
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 1 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: ##############
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 1 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: ##############
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 2 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: [0x101007040] ################ POOL 0x101007040
objc[2934]: ##############
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 3 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: [0x101007040] ################ POOL 0x101007040
objc[2934]: [0x101007048] 0x100c4df10 __NSCFString
objc[2934]: ##############
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 3 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: [0x101007040] ################ POOL 0x101007040
objc[2934]: [0x101007048] 0x100c4df10 __NSCFString
objc[2934]: ##############
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 3 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: [0x101007040] ################ POOL 0x101007040
objc[2934]: [0x101007048] 0x100c4df10 __NSCFString
objc[2934]: ##############
2018-04-15 01:11:21.034998+0800 debug-objc[2934:268908] hello every body
2018-04-15 01:11:21.035301+0800 debug-objc[2934:268908] hello every body
2018-04-15 01:11:21.035331+0800 debug-objc[2934:268908] hello every body
2018-04-15 01:11:21.035348+0800 debug-objc[2934:268908] hello every body
2018-04-15 01:11:21.035361+0800 debug-objc[2934:268908] hello every body
2018-04-15 01:11:21.035369+0800 debug-objc[2934:268908] hello every body
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 3 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: [0x101007040] ################ POOL 0x101007040
objc[2934]: [0x101007048] 0x100c4df10 __NSCFString
objc[2934]: ##############
objc[2934]: ##############
objc[2934]: AUTORELEASE POOLS for thread 0x1009ba340
objc[2934]: 1 releases pending.
objc[2934]: [0x101007000] ................ PAGE (hot) (cold)
objc[2934]: [0x101007038] 0x100c4df10 __NSCFString
objc[2934]: ##############
與大家想的是否有出入呢,有出入可以自己先思考下。在分析代碼之前我們快速過(guò)一下ARC機(jī)制的一些知識(shí),過(guò)完相信大家會(huì)知曉答案如此產(chǎn)生的過(guò)程。
2.ARC下的內(nèi)存管理關(guān)鍵字
(1)引用計(jì)數(shù)機(jī)制與__ strong
OC的堆區(qū)對(duì)象生成后是由對(duì)象指針?biāo)钟?,通過(guò)對(duì)象指針進(jìn)行方法訪問(wèn),程序通過(guò)對(duì)象指針進(jìn)行對(duì)象的處理。所以在多個(gè)對(duì)象指針持有對(duì)象的情況下,必須要確定釋放對(duì)象的正確時(shí)機(jī)。
必須有一種機(jī)制來(lái)記錄其持有者數(shù)量,當(dāng)持有者數(shù)量為0時(shí)釋放對(duì)象。在OC中這一方式稱(chēng)為引用計(jì)數(shù)機(jī)制,即記錄持有該對(duì)象的對(duì)象指針變量數(shù)目(retainCount)。每個(gè)對(duì)象指針擁有對(duì)象通過(guò)給對(duì)象的retainCount+1確保自己擁有對(duì)象(即retain方法),通過(guò)給對(duì)象的retainCount-1確保放棄對(duì)象的所有權(quán)(即`release方法)。顯然retain與release必須成對(duì)出現(xiàn),進(jìn)行對(duì)象指針的聲明持有與放棄持有操作。OC通過(guò)一個(gè)sideTableMap的哈希表來(lái)實(shí)現(xiàn)引用計(jì)數(shù)的管理。key是對(duì)象指針,值是retainCount。導(dǎo)致計(jì)數(shù)值改變的對(duì)象指針我們稱(chēng)為強(qiáng)持有,即__ strong 來(lái)修飾,對(duì)象指針默認(rèn)無(wú)修飾情況下__ strong。最后引用計(jì)數(shù)為0,釋放對(duì)象的工作也由release方法中調(diào)用dealloc完成。
(2)__weak的使用
考慮到兩個(gè)對(duì)象互相引用的情況,通過(guò)__ weak來(lái)避免無(wú)法釋放對(duì)象的情況。即分為_(kāi)_ strong和__ weak兩種,__ weak修飾的指針不占用引用計(jì)數(shù),即沒(méi)有所有權(quán)。在對(duì)象釋放后會(huì)置weak 指針為nil,也是通過(guò)一個(gè)哈希表來(lái)進(jìn)行查找置nil。key是對(duì)象指針,找到該對(duì)象對(duì)應(yīng)的所有weak變量。
(3)__autoreleasing的使用
再考慮另一種情況,在引用計(jì)數(shù)機(jī)制出現(xiàn)里我們提到,對(duì)象指針持有對(duì)象必須保證retain和release的成對(duì)進(jìn)行,即我聲明了所有權(quán)也該我放棄所有權(quán),這樣能保證引用計(jì)數(shù)的正確性,那么考慮不能及時(shí)釋放的情況,這種情況多是夸作用域需要擔(dān)憂的。
NSMutableString * test(){
NSMutableString * st = [[NSMutableString alloc] initWithFormat:@"hello every body"];
return st;
}
main(){
NSMutableString * s = test();
}
這里我們申請(qǐng)了一個(gè)NSMutableString對(duì)象,并且返回了這一對(duì)象指針。首先st作為無(wú)關(guān)鍵字修飾的指針,根據(jù)原則為_(kāi)_strong類(lèi)型,而返回的st實(shí)質(zhì)是對(duì)st復(fù)制的臨時(shí)變量,我們標(biāo)記為obj,也會(huì)指向?qū)ο蟆?/p>
st指針在return st;語(yǔ)句后作用域結(jié)束,生命周期結(jié)束前按照管理原則會(huì)進(jìn)行[st release]將retainCount-1表明所有者-1,放棄所有權(quán)。但是問(wèn)題來(lái)了,我們顯然要保留對(duì)象給調(diào)用者使用,所以我們復(fù)制的臨時(shí)變量obj會(huì)增加計(jì)數(shù)值,表明自己持有對(duì)象,即[obj retain]。但是接著出現(xiàn)的問(wèn)題是,無(wú)名臨時(shí)變量obj在返回給調(diào)用者后即消亡,無(wú)法在消亡前進(jìn)行[obj release]放棄所有權(quán)的責(zé)任。需要有一種機(jī)制幫其實(shí)現(xiàn)[obj release]使得引用計(jì)數(shù)-1,放棄所有權(quán),這就是autorelease pool的意義。只要將obj壓入autorelease pool,這時(shí)obj只進(jìn)行了聲明所有權(quán),即retainCount+1的操作;而本該它完成的放棄所有權(quán),即retainCount-1的任務(wù)交由autorelease pool壓入的對(duì)象指針進(jìn)行,進(jìn)行時(shí)機(jī)是autorelease pool生命周期結(jié)束時(shí),依次彈棧,并向pool內(nèi)對(duì)象指針調(diào)用release方法。
編譯器發(fā)現(xiàn)return st;這種返回對(duì)象指針變量時(shí),所返回的無(wú)名對(duì)象指針變量實(shí)際會(huì)進(jìn)行類(lèi)似如下處理。
NSMutableString * test(){
NSMutableString * st = [[NSMutableString alloc] initWithFormat:@"hello every body"];
NSMutableString * __autoreleasing obj = st;
return obj;
}
__ autoreleasing關(guān)鍵字,編譯器遇到它會(huì)調(diào)用[obj objc_retainAutorelease];
這個(gè)方法等同于[obj retain]+[obj autorelease];即無(wú)名臨時(shí)變量的聲明所有權(quán)+壓入autorelease pool讓其代替進(jìn)行放棄所有權(quán)的責(zé)任。
至于對(duì)象指針指向其他對(duì)象時(shí)的行為
//強(qiáng)引用兩種
str = obj; //其他對(duì)象地址值
str = str; //warning:自賦值操作
//賦值會(huì)調(diào)用NSObject.mm中的objc_storeStrong,
//其中l(wèi)ocation是指向該對(duì)象指針的指針,obj為新對(duì)象地址
//它自己用了strong指針對(duì)象變量的指針,調(diào)用retain,release進(jìn)行修改
void
objc_storeStrong(id *location/*對(duì)象指針變量*/, id obj)
{
id prev = *location;
if (obj == prev) { //自賦值檢測(cè)
return;
}
objc_retain(obj); //聲明對(duì)新對(duì)象所有權(quán)
*location = obj; //指向新對(duì)象
objc_release(prev); //放棄原對(duì)象所有權(quán)
}
//弱引用兩種,自賦值和他值都是修改weakTable為新對(duì)象dealloc時(shí)置nil。
//autoreleasing兩種,不管是否相同,增加所有權(quán)計(jì)數(shù),壓入release pool托管release責(zé)任。
所以由autorelease pool托管release責(zé)任,解決了retain/release出現(xiàn)跨作用域分隔的問(wèn)題。通過(guò)autorelease pool托管放棄所有權(quán)這一步,看起來(lái)解決了計(jì)數(shù)機(jī)制的返回對(duì)象指針的不成對(duì)操作(只retain,沒(méi)release)問(wèn)題,但是顯然autorelease pool如果釋放的遲的話會(huì)導(dǎo)致對(duì)象的延遲釋放。所以我們很少見(jiàn)到作用域內(nèi)顯示使用__ autoreleasing,因?yàn)樵谧饔糜騼?nèi),默認(rèn)的__ strong對(duì)象指針能在作用域結(jié)束及時(shí)release,下面會(huì)舉一個(gè)例子
(4)autorelease pool的使用
autorelease pool負(fù)責(zé)將不在同一個(gè)作用域內(nèi)的對(duì)象指針變量retain/release中的release方法托管,在自身結(jié)束后延遲釋放。
先看常見(jiàn)的錯(cuò)誤代碼示范,這一例子我們常見(jiàn)于舉例何時(shí)使用@autoreleasepool{}塊這樣的問(wèn)題中
- (void)useALoadOfNumbers {
for (int j = 0; j < 100000; ++j) {
@autoreleasepool {
for (int i = 0; i < 100; ++i) {
//NSNumber *number = [[NSNumber alloc] initWithInt:(i+j)];
//NSLog(@"number = %p", number);
}
}
}
}
在這樣的示例代碼中,很多人常常會(huì)加一句這樣能夠避免內(nèi)存峰值過(guò)高的風(fēng)險(xiǎn),really?顯然不是,即使你注釋掉@autoreleasepool{}也是基本相同的內(nèi)存占用,為什么?(可以思考下,1-2min)
看了之前的鋪墊內(nèi)容,我們可能會(huì)這樣說(shuō),答案很簡(jiǎn)單,number無(wú)關(guān)鍵字修飾,編譯器默認(rèn)處理為_(kāi)_ strong,在內(nèi)層for(){}作用域結(jié)束,number的生命周期結(jié)束,顯然ARC會(huì)幫我們插入[number release]放棄所有權(quán),這時(shí)引用計(jì)數(shù)為0,release中調(diào)用dealloc方法釋放內(nèi)存,所以不會(huì)有所謂的峰值過(guò)高風(fēng)險(xiǎn)。那什么時(shí)候有。我們只要稍加修改代碼
- (void)useALoadOfNumbers {
for (int j = 0; j < 100000; ++j) {
@autoreleasepool
{
for (int i = 0; i < 100; ++i) {
NSMutableString * test = [NSMutableString stringWithFormat:@"hello every body"];; //改為類(lèi)方法
NSLog(@"%2", test);
// NSNumber *number = [NSNumber numberWithInt:(i+j)]; //改為類(lèi)方法
// NSLog(@"number = %p", number);
}
}
}
}
等等,這里我們?yōu)楹巫⑨屃薔SNumber對(duì)象類(lèi)型?原因一會(huì)就講,這里先略過(guò),在新代碼中如果注釋掉@autoreleasepool{}塊就該會(huì)出現(xiàn)所預(yù)料的內(nèi)存峰值,而保留則不會(huì)。


原因何在?我們看到我們的對(duì)象是通過(guò)類(lèi)方法調(diào)用返回對(duì)象指針,而類(lèi)方法實(shí)際是通過(guò)函數(shù)返回對(duì)象指針?lè)绞剑纠缦?/p>
+ (id) array
{
return [[NSMutableArray alloc] init];
}
如我們所說(shuō),遇到return對(duì)象指針編譯器會(huì)生成一個(gè)__ autoreleasing 無(wú)名臨時(shí)變量,調(diào)用[obj objc_retainAutorelease];。
現(xiàn)在我們來(lái)看替換對(duì)象類(lèi)型的原因,我們得注意不是所有的對(duì)象都生成在堆區(qū)。所以有些人的示例代碼也是錯(cuò)的,即使是大牛,比如在StackOverflow一個(gè)問(wèn)題,[Why is @autoreleasepool still needed with ARC?]上,我們找到了《Effective Objective-C 2.0》作者mattjgalloway的回答[mattjgalloway's answer],方便查看復(fù)制如下。
An example of using an auto release pool:
- (void)useALoadOfNumbers { for (int j = 0; j < 10000; ++j) { @autoreleasepool { for (int i = 0; i < 10000; ++i) { NSNumber *number = [NSNumber numberWithInt:(i+j)]; NSLog(@"number = %p", number); } } } }
其實(shí)這并不會(huì)使得內(nèi)存出現(xiàn)峰值,為何?因?yàn)橐粋€(gè)8字節(jié)指針內(nèi)部有很多的閑置bit,利用taggedpointer機(jī)制,NSNumber對(duì)象會(huì)被處理到指針中,成為存活在棧的假對(duì)象,有關(guān)利用taggedpointer優(yōu)化的知識(shí)在這里就不深入了。這也是我們?yōu)楹卧谘菔局刑鎿Q為NSMutableString的原因。輸出結(jié)果給大家看下來(lái)作為佐證。
2018-04-16 10:50:20.315737+0800 test[1360:43066] number = 0x27
2018-04-16 10:50:20.315758+0800 test[1360:43066] number = 0x127
2018-04-16 10:50:20.315773+0800 test[1360:43066] number = 0x227
2018-04-16 10:50:20.315785+0800 test[1360:43066] number = 0x327
2018-04-16 10:50:20.315797+0800 test[1360:43066] number = 0x427
2018-04-16 10:50:20.315809+0800 test[1360:43066] number = 0x527
2018-04-16 10:50:20.315821+0800 test[1360:43066] number = 0x627
2018-04-16 10:50:20.315833+0800 test[1360:43066] number = 0x727
2018-04-16 10:50:20.315856+0800 test[1360:43066] number = 0x827
2018-04-16 10:50:20.315887+0800 test[1360:43066] number = 0x927
2018-04-16 10:50:20.315903+0800 test[1360:43066] number = 0xa27
地址中存了值的假對(duì)象就露出原形了。
2.總結(jié)流程
(1)__strong * objcStrong:
聲明所有權(quán):
[objcStrong retain] —— > self->rootretain() —— > sideTable(self)通過(guò)哈希表找到/添加引用計(jì)數(shù)
— — >retainCount++ ;
放棄所有權(quán):
[objcStrong release] —— > self->rootRelease() — — >sideTable(self)通過(guò)哈希表找到引用計(jì)數(shù)
— — >retainCount -1 — — >if(retainCount)判定計(jì)數(shù)值— — > dealloc(self) 釋放對(duì)象
— — >table.refcnts.erase();//刪除引用計(jì)數(shù)器
(2)__weak * objcWeak
注冊(cè)到哈希表:
storeWeak(&objc, newObj)將弱引用變量地址存入哈希表— — > sideTable(newObj) 找到對(duì)象的弱引
用指針地址表位置— — >weak_register_no_lock(*, newObj, &objc, *);注冊(cè)弱引用指針位置
對(duì)象釋放時(shí):
dealloc釋放對(duì)象 — — >weak_clear_no_lock(weak_table_t *weak_table, id referent_id);查找對(duì)象的弱引用數(shù)組并置弱引用指針nil — — >weak_entry_remove(weak_table, entry);刪除對(duì)象對(duì)于弱引用表
(3)__autorelease *objcAuto
聲明所有權(quán)并托管釋放權(quán):
objc_retainAutoreleasedReturnValue(id objcAuto) — — >[objcAuto retain]增加引用計(jì)數(shù)
— — > [obj autorelease]調(diào)用自動(dòng)釋放方法 — —> poolPage -> push(objcAuto);壓入autoreleasepool
3.內(nèi)存管理小結(jié):
retainCount的本質(zhì)是記錄調(diào)用retain的次數(shù),所以確保引用計(jì)數(shù)與所有者一致必須確保release/retain成對(duì)出現(xiàn)。且是在對(duì)對(duì)象指針賦值時(shí),根據(jù)修飾符為_(kāi)_ strong(copy時(shí)也是__ strong)、__ autoreleasing、__ weak做出不同的處理,而與對(duì)象的生成階段(即與alloc/new/copy)無(wú)關(guān),當(dāng)然另一個(gè)值得注意的是生成的對(duì)象未必在堆區(qū),這也是MRC下retainCount出現(xiàn)-1的原因。
-
對(duì)于無(wú)法及時(shí)插入release方法的,通過(guò)自動(dòng)釋放池autorelease pool代為release,確保release/retain的成對(duì)操作。
來(lái)不及調(diào)用release就消亡,如函數(shù)返回對(duì)象指針,包含了類(lèi)方法,autorelease pool幫助它放棄所有權(quán)。
@autoreleasepool{}的使用場(chǎng)景在于大量生成autorelease對(duì)象于某一作用域。如如上提到的兩種類(lèi)型。因?yàn)槊看蝃obj autorelease]進(jìn)入的都是頂層pool,合理的插入@autoreleasepool{}能夠相對(duì)提前放棄我們autorelease對(duì)象的所有權(quán),避免內(nèi)存峰值。而消耗就是存幾個(gè)對(duì)象指針,滿(mǎn)的時(shí)候也就再多一個(gè)4KB的page,獲得卻很大。
ARC下,編譯器幫我們做了前兩步,最后一步的@autoreleasepool{}提前釋放自己{}內(nèi)的autorelease對(duì)象需要我們自己去取舍處理。另一方面為何蘋(píng)果公司不推薦使用如類(lèi)方法這種便利方法的原因,也是其為函數(shù)返回對(duì)象指針的早夭性質(zhì)存入autorelease pool,導(dǎo)致延遲釋放。但是@autoreleasepool也存在風(fēng)險(xiǎn),如我們提到的,我們會(huì)考慮用autorelease pool去解決對(duì)象指針早早拋棄對(duì)象離去來(lái)不及release的問(wèn)題,那么一但autorelease pool比我們的__ autoreleasing對(duì)象指針先結(jié)束,則可能因?yàn)閷?duì)象消亡會(huì)遇到bad access的運(yùn)行錯(cuò)誤。
如下面代碼編譯有無(wú)問(wèn)題,運(yùn)行有無(wú)問(wèn)題?
#import <Foundation/Foundation.h>
extern void _objc_autoreleasePoolPrint(void);
int main()
{
NSMutableString __autoreleasing * autorePointer = nil;
NSMutableString __strong * strongPointer = nil;
NSLog(@"autorePointer is :%p",autorePointer);
_objc_autoreleasePoolPrint();
@autoreleasepool{
_objc_autoreleasePoolPrint(); //輸出結(jié)果及原因,問(wèn)題1
NSMutableString * a = [[NSMutableString alloc] initWithFormat:@"hello every body"];
strongPointer = [[NSMutableString alloc] initWithFormat:@"大吉大利,今晚吃雞"];
autorePointer = a;
NSLog(@"strongPointer address is :%p",autorePointer);
NSLog(@"autorePointer address is :%p",autorePointer);
NSLog(@"%@",strongPointer);
_objc_autoreleasePoolPrint();
}
NSLog(@"%@",strongPointer);
NSLog(@"autorePointer address is :%p",autorePointer);
_objc_autoreleasePoolPrint();
NSUInteger len = [strongPointer length];
NSLog(@"%lu",len);
len = [autorePointer length];
NSLog(@"%lu",len);
return 0;
}
這題還是很有趣的,有一個(gè)小點(diǎn)會(huì)考察到永昌昨天分享的知識(shí)點(diǎn),即問(wèn)題1的疑問(wèn),答案是當(dāng)沒(méi)有真正的對(duì)象指針壓入時(shí)是不會(huì)生成棧,這是一種內(nèi)存優(yōu)化。所問(wèn)題1打印【 0 releases pending】,壓入autorePointer打印【 2 releases pending】
答案如下
運(yùn)行會(huì)大概率BAD ACCESS錯(cuò)誤,Thread 1: EXC_BAD_ACCESS (code=1, address=0x10049690),出現(xiàn)再NSUInteger len = [autorePointer length];
autorePointer is :0x0
objc[4493]: ##############
objc[4493]: 0 releases pending.
objc[4493]: ##############
objc[4493]: ##############
objc[4493]: 0 releases pending.
objc[4493]: ##############
strongPointer address is :0x10043e2b0
autorePointer address is :0x10043e2b0
大吉大利,今晚吃雞
objc[4493]: ##############
objc[4493]: 2 releases pending.
objc[4493]: ##############
大吉大利,今晚吃雞
autorePointer address is :0x10043e2b0
objc[4493]: ##############
objc[4493]: 0 releases pending.
objc[4493]: ##############
Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)//出現(xiàn)在[autorePointer length];
總的來(lái)說(shuō)我們極少需要使用@autoreleasepool{}去對(duì)內(nèi)存進(jìn)行托管釋放,分散開(kāi)的少量的類(lèi)方法對(duì)象進(jìn)入runloop的自動(dòng)釋放池,在每次runloop更新中釋放,大量集中的autorelease對(duì)象才會(huì)使用到pool塊,但是例如遇到上面的bad access問(wèn)題時(shí)候我們應(yīng)該知道為何如此。
值得注意的是當(dāng)我們用__weak對(duì)象指針進(jìn)行某些操作時(shí),會(huì)調(diào)用到objc_loadWeak(id *location);導(dǎo)致出現(xiàn)增加引用計(jì)數(shù)和autorelease pool托管釋放的的情況。
id
objc_loadWeak(id *location)
{
if (!*location) return nil;
return objc_autorelease(objc_loadWeakRetained(location));
}
通過(guò)runtime,對(duì)此調(diào)用的,caller只有NSObject.mm提供的接口里object_getIvar()直接調(diào)用該方法,object_getInstanceVariable()通過(guò)object_getIvar()間接調(diào)用,別的沒(méi)有caller。方法作用是獲取類(lèi)實(shí)例對(duì)象中的實(shí)例變量的值,具體使用未進(jìn)行探究,做個(gè)標(biāo)記。
以上就是ARC內(nèi)存管理的淺析,個(gè)人理解,希望大家糾錯(cuò)補(bǔ)充。