內(nèi)存管理和分配
內(nèi)存分為5個(gè)區(qū)域澡匪,分別指的是----->棧區(qū)/堆區(qū)/BSS段/數(shù)據(jù)段/代碼段
棧:存儲(chǔ)局部變量,當(dāng)其作用域執(zhí)行完畢之后褒链,就會(huì)被系統(tǒng)立即收回
堆:存儲(chǔ)OC對(duì)象唁情,手動(dòng)申請(qǐng)的字節(jié)空間,需要調(diào)用free來(lái)釋放
BSS段:未初始化的全局變量和靜態(tài)變量甫匹,一旦初始化就會(huì)從BSS段中回收掉甸鸟,轉(zhuǎn)存到數(shù)據(jù)段中
數(shù)據(jù)段:存儲(chǔ)已經(jīng)初始化的全局變量和靜態(tài)變量,以及常量數(shù)據(jù)兵迅,直到結(jié)束程序時(shí)才會(huì)被立即收回
代碼段:代碼抢韭,直到結(jié)束程序時(shí)才會(huì)被立即收回
除了堆區(qū),其他區(qū)域的中存儲(chǔ)的數(shù)據(jù)恍箭,都是由系統(tǒng)自動(dòng)釋放的刻恭,堆區(qū)中的OC對(duì)象,是不會(huì)自動(dòng)釋放的扯夭,如果不主動(dòng)釋放鳍贾,那么將在程序結(jié)束的時(shí)候才去釋放
舉例:
當(dāng)?shù)谝粋€(gè)人進(jìn)入辦公室時(shí),他需要使用燈交洗,于是開燈骑科,引用計(jì)數(shù)為1
當(dāng)另一個(gè)人進(jìn)入辦公室時(shí),他也需要燈构拳,引用計(jì)數(shù)為2咆爽;每當(dāng)多一個(gè)人進(jìn)入辦公室時(shí)梁棠,引用計(jì)數(shù)加1
當(dāng)有一個(gè)人離開辦公室時(shí),引用計(jì)數(shù)減1伍掀,當(dāng)引用計(jì)數(shù)為0時(shí)掰茶,也就是最后一個(gè)人離開辦公室時(shí),他不再需要使用燈蜜笤,關(guān)燈離開辦公室濒蒋。
當(dāng)你alloc一個(gè)對(duì)象objc,此時(shí)RC=1把兔;在某個(gè)地方你又retain這個(gè)對(duì)象objc沪伙,此時(shí)RC加1,也就是RC=2县好;由于調(diào)用alloc/retain一次围橡,對(duì)應(yīng)需要調(diào)用release一次來(lái)釋放對(duì)象objc,所以你需要release對(duì)象objc兩次缕贡,此時(shí)RC=0翁授;而當(dāng)RC=0時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用dealloc方法釋放對(duì)象晾咪。
Autorelease Pool ?自動(dòng)釋放池
在釋放池中的調(diào)用了autorelease方法的對(duì)象都會(huì)被壓在該池的頂部(以棧的形式管理對(duì)象)收擦。當(dāng)自動(dòng)釋放池被銷毀的時(shí)候,在該池中的對(duì)象會(huì)自動(dòng)調(diào)用release方法來(lái)釋放資源谍倦,銷毀對(duì)象塞赂。以此來(lái)達(dá)到自動(dòng)管理內(nèi)存的目的。
在開發(fā)中昼蛀,我們常常都會(huì)使用到局部變量宴猾,局部變量一個(gè)特點(diǎn)就是當(dāng)它超過(guò)作用域時(shí),就會(huì)自動(dòng)釋放叼旋。而autorelease pool跟局部變量類似仇哆,當(dāng)執(zhí)行代碼超過(guò)autorelease pool塊時(shí),所有放在autorelease pool的對(duì)象都會(huì)自動(dòng)調(diào)用release夫植。它的工作原理如下:
?RunLoop :?1(等待消息) -> 2(將要處理消息) -> 3(處理消息) -> 4(消息處理完成) -> 1(等待消息)
你也可以將消息(message)這個(gè)詞換成信號(hào)(signal)或者事件(Event)税产。當(dāng)沒(méi)有消息到來(lái)的時(shí)候,這個(gè)線程就會(huì)休眠偷崩,等待消息到來(lái)后觸發(fā)處理過(guò)程。
AutoreleasePool是在什么時(shí)候創(chuàng)建的撞羽,又是在什么時(shí)候被銷毀阐斜?
App啟動(dòng)后,系統(tǒng)在主線程RunLoop 里注冊(cè)兩個(gè)Observser,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()诀紊。
第一個(gè) Observer 監(jiān)視的事件?
是 Entry(即將進(jìn)入Loop)谒出,其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池。其優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前笤喳。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件
_BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)_
調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池为居;
_Exit(即將退出Loop) 時(shí)_
調(diào)用 _objc_autoreleasePoolPop() 來(lái)釋放自動(dòng)釋放池。這個(gè) Observer 優(yōu)先級(jí)最低杀狡,保證其釋放池子發(fā)生在其他所有回調(diào)之后蒙畴。
在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)呜象、Timer回調(diào)內(nèi)的膳凝。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏恭陡,開發(fā)者也不必顯示創(chuàng)建 Pool 了蹬音。
現(xiàn)在我們知道了AutoreleasePool是在RunLoop即將進(jìn)入RunLoop和準(zhǔn)備進(jìn)入休眠這兩種狀態(tài)的時(shí)候被創(chuàng)建和銷毀的。
所以AutoreleasePool的釋放有如下兩種情況休玩。
一是Autorelease對(duì)象是在當(dāng)前的runloop迭代結(jié)束時(shí)釋放的著淆,而它能夠釋放的原因是系統(tǒng)在每個(gè)runloop迭代中都加入了自動(dòng)釋放池Push和Pop。
二是手動(dòng)調(diào)用AutoreleasePool的釋放方法(drain方法)來(lái)銷毀AutoreleasePool
創(chuàng)建一個(gè)NSAutoreleasePool對(duì)象
在autorelease pool塊的對(duì)象調(diào)用autorelease方法
釋放NSAutoreleasePool對(duì)象
NSAutoreleasePool?*pool?=?[[NSAutoreleasePool?alloc]?init];
//?put?object?into?pool
id?obj?= [[[NSObject ?alloc] ?init] ?autorelease];
[pool?drain];
/*?超過(guò)autorelease?pool作用域范圍時(shí)拴疤,obj會(huì)自動(dòng)調(diào)用release方法?*/
由于放在autorelease pool的對(duì)象并不會(huì)馬上釋放永部,如果有大量圖片數(shù)據(jù)放在這里的話遥赚,將會(huì)導(dǎo)致內(nèi)存不足扬舒。(一次runloop結(jié)束后釋放)
for(int?i?=?0;?i?<?numberOfImages;?i++)
{
??????/*???處理圖片,例如加載
???????*???太多autoreleased?objects存在
???????*???由于NSAutoreleasePool對(duì)象沒(méi)有被釋放
???????*???在某個(gè)時(shí)刻,會(huì)導(dǎo)致內(nèi)存不足?
???????*/
}
在ARC內(nèi)存管理機(jī)制中,id和其他對(duì)象類型變量必須是以下四個(gè)ownership qualifiers其中一個(gè)來(lái)修飾:
__strong(默認(rèn)费奸,如果不指定其他,編譯器就默認(rèn)加入)
__weak
__unsafe_unretained
__autoreleasing
__strong ownership qualifier
如果我想創(chuàng)建一個(gè)字符串丛肮,使用完之后將它釋放調(diào)用,使用MRC管理內(nèi)存的寫法應(yīng)該是這樣:
{
????NSString?*text?=?[[NSString?alloc]?initWithFormat:@"Hello,?world"];???//@"Hello,?world"對(duì)象的RC=1
????NSLog(@"%@",?text);
????[text?release];??????????????????????//@"Hello,?world"對(duì)象的RC=0
}
而如果是使用ARC方式的話,就text對(duì)象無(wú)需調(diào)用release方法桩皿,而是當(dāng)text變量超過(guò)作用域時(shí)暖呕,編譯器來(lái)自動(dòng)加入[text release]方法來(lái)釋放內(nèi)存
{
????NSString?*text?=?[[NSString?alloc]?initWithFormat:@"Hello,?world"];????//@"Hello,?world"對(duì)象 RC=1
????NSLog(@"%@",?text);
}
/* ?當(dāng)text超過(guò)作用域時(shí)库物,@"Hello,?world"對(duì)象會(huì)自動(dòng)釋放镀虐,RC=0 ? */
而當(dāng)你將text賦值給其他變量anotherText時(shí)辈毯,MRC需要retain一下來(lái)持有所有權(quán),當(dāng)text和anotherText使用完之后搜贤,各個(gè)調(diào)用release方法來(lái)釋放谆沃。{
????NSString?*text?=?[[NSString?alloc]?initWithFormat:@"Hello,?world"];????//@"Hello,?world"對(duì)象 RC=1
????NSLog(@"%@",?text);
? ? NSString?*anotherText?=?text;????????//@"Hello,?world"對(duì)象的RC=1
????[anotherText?retain];????????????????//@"Hello,?world"對(duì)象的RC=2
????NSLog(@"%@",?anotherText);
? ? [text?release];??????????????????????//@"Hello,?world"對(duì)象的RC=1
????[anotherText?release];???????????????//@"Hello,?world"對(duì)象的RC=0
}
而使用ARC的話,并不需要調(diào)用retain和release方法來(lái)持有跟釋放對(duì)象仪芒。{
????NSString?*text?=?[[NSString?alloc]?initWithFormat:@"Hello,?world"];???//@"Hello,?world"對(duì)象的RC=1
????NSLog(@"%@",?text);
????NSString?*anotherText?=?text;????????//@"Hello,?world"對(duì)象的RC=2
????NSLog(@"%@",?anotherText);
}
/ *??當(dāng)text和anotherText超過(guò)作用域時(shí)唁影,會(huì)自動(dòng)調(diào)用[text?release]和[anotherText?release]方法 ??
@"Hello,?world"對(duì)象的RC=0 ? */
除了當(dāng)__strong變量超過(guò)作用域時(shí),編譯器會(huì)自動(dòng)加入release語(yǔ)句來(lái)釋放內(nèi)存掂名,如果你將__strong變量重新賦給它其他值据沈,那么編譯器也會(huì)自動(dòng)加入release語(yǔ)句來(lái)釋放變量指向之前的對(duì)象。例如:
{
????NSString?*text?=?[[NSString?alloc]?initWithFormat:@"Hello,?world"];????//@"Hello,?world"對(duì)象的RC=1
????NSString?*anotherText?=?text;????????//@"Hello,?world"對(duì)象的RC=2
????NSString?*anotherText?=?[[NSString?alloc]?initWithFormat:@"Sam?Lau"];??//?由于anotherText對(duì)象引用另一個(gè)對(duì)象@"Sam?Lau"饺蔑,那么就會(huì)自動(dòng)調(diào)用[anotherText?release]方法锌介,使得@"Hello,?world"對(duì)象的RC=1,?@"Sam?Lau"對(duì)象的RC=1
}
/*
?*??當(dāng)text和anotherText超過(guò)作用域時(shí),會(huì)自動(dòng)調(diào)用[text?release]和[anotherText?release]方法猾警,
?*??@"Hello,?world"對(duì)象的RC=0和@"Sam?Lau"對(duì)象的RC=0
?*/
我們總結(jié)一下編譯器是按以下方法來(lái)實(shí)現(xiàn)的:
對(duì)于規(guī)則1和規(guī)則2孔祸,是通過(guò)__strong變量來(lái)實(shí)現(xiàn),
對(duì)于規(guī)則3來(lái)說(shuō)肿嘲,當(dāng)變量超過(guò)它的作用域或被賦值或成員變量被丟棄時(shí)就能實(shí)現(xiàn)
對(duì)于規(guī)則4融击,當(dāng)RC=0時(shí),系統(tǒng)就會(huì)自動(dòng)調(diào)用
__weak ownership qualifier
其實(shí)編譯器根據(jù)__strong修飾符來(lái)管理對(duì)象內(nèi)存雳窟。但是__strong并不能解決引用循環(huán)(Reference Cycle)問(wèn)題:對(duì)象A持有對(duì)象B尊浪,反過(guò)來(lái),對(duì)象B持有對(duì)象A封救;這樣會(huì)導(dǎo)內(nèi)存造成內(nèi)存泄露問(wèn)題拇涤。
@interface?Test?:?NSObject
@property?(strong,?nonatomic)?id?objc;
@end
{
????Test?*test1?=?[Test?new];????????/*?對(duì)象a?*/
????/*?test1有一個(gè)強(qiáng)引用到對(duì)象a?*/
????Test?*test2?=?[Test?new];????????/*?對(duì)象b?*/
????/*?test2有一個(gè)強(qiáng)引用到對(duì)象b?*/
????test1.objc?=?test2;??????????????/*?對(duì)象a的成員變量objc有一個(gè)強(qiáng)引用到對(duì)象b?*/
????test2.objc?=?test1;??????????????/*?對(duì)象b的成員變量objc有一個(gè)強(qiáng)引用到對(duì)象a?*/
}
/*???當(dāng)變量test1超過(guò)它作用域時(shí),它指向a對(duì)象會(huì)自動(dòng)release
?*???當(dāng)變量test2超過(guò)它作用域時(shí)誉结,它指向b對(duì)象會(huì)自動(dòng)release
?*???此時(shí)鹅士,b對(duì)象的objc成員變量仍持有一個(gè)強(qiáng)引用到對(duì)象a
?*???此時(shí),a對(duì)象的objc成員變量仍持有一個(gè)強(qiáng)引用到對(duì)象b
?*???于是發(fā)生內(nèi)存泄露
?*/
如何解決惩坑?于是我們引用一個(gè)__weakownership qualifier掉盅,被它修飾的變量都不持有對(duì)象的所有權(quán)也拜,而且當(dāng)變量指向的對(duì)象的RC為0時(shí),變量設(shè)置為nil趾痘。例如:
__weak?NSString?*text?=?[[NSString?alloc]?initWithFormat:@"Sam?Lau"];
NSLog(@"%@",?text);
由于text變量被__weak修飾慢哈,text并不持有@"Sam Lau"對(duì)象的所有權(quán),@"Sam Lau"對(duì)象一創(chuàng)建就馬上被釋放永票,并且編譯器給出警告卵贱,所以打印結(jié)果為(null)。
所以侣集,針對(duì)剛才的引用循環(huán)問(wèn)題键俱,只需要將Test類的屬性objc設(shè)置weak修飾符,那么就能解決世分。
@property?(weak,?nonatomic)?id?objc;
__unsafe_unretained ownership qualifier
__unsafe_unretained ownership qualifier编振,正如名字所示,它是不安全的罚攀。它跟__weak相似党觅,被它修飾的變量都不持有對(duì)象的所有權(quán),但當(dāng)變量指向的對(duì)象的RC為0時(shí)斋泄,變量并不設(shè)置為nil杯瞻,而是繼續(xù)保存對(duì)象的地址;這樣的話炫掐,對(duì)象有可能已經(jīng)釋放魁莉,但繼續(xù)訪問(wèn),就會(huì)造成非法訪問(wèn)(Invalid Access)募胃。例子如下:
__unsafe_unretained?id?obj0?=?nil;
{
????id?obj1?=?[[NSObject?alloc]?init];?????//?對(duì)象A
????/*?由于obj1是強(qiáng)引用旗唁,所以obj1持有對(duì)象A的所有權(quán),對(duì)象A的RC=1?*/
????obj0?=?obj1;
????/*?由于obj0是__unsafe_unretained痹束,它不持有對(duì)象A的所有權(quán)检疫,但能夠引用它,對(duì)象A的RC=1?*/
????NSLog(@"A:?%@",?obj0);
}
/*?當(dāng)obj1超過(guò)它的作用域時(shí)祷嘶,它指向的對(duì)象A將會(huì)自動(dòng)釋放?*/
NSLog(@"B:?%@",?obj0);
/*?由于obj0是__unsafe_unretained屎媳,當(dāng)它指向的對(duì)象RC=0時(shí),它會(huì)繼續(xù)保存對(duì)象的地址论巍,所以兩個(gè)地址相同?*/
如果將__unsafe_unretained改為weak的話烛谊,兩個(gè)打印結(jié)果將不同
__weak?id?obj0?=?nil;
{
????id?obj1?=?[[NSObject?alloc]?init];?????//?對(duì)象A
????/*?由于obj1是強(qiáng)引用,所以obj1持有對(duì)象A的所有權(quán)嘉汰,對(duì)象A的RC=1?*/
????obj0?=?obj1;
????/*?由于obj0是__unsafe_unretained丹禀,它不持有對(duì)象A的所有權(quán),但能夠引用它,對(duì)象A的RC=1?*/
????NSLog(@"A:?%@",?obj0);
}
/*?當(dāng)obj1超過(guò)它的作用域時(shí)双泪,它指向的對(duì)象A將會(huì)自動(dòng)釋放?*/
NSLog(@"B:?%@",?obj0);
/*?由于obj0是__weak持搜,?當(dāng)它指向的對(duì)象RC=0時(shí),它會(huì)自動(dòng)設(shè)置為nil焙矛,所以兩個(gè)打印結(jié)果將不同*/
__autoreleasing ownership qualifier
引入ARC之后朵诫,讓我們看看autorelease pool有哪些變化。沒(méi)有ARC之前的寫法如下:
NSAutoreleasePool?*pool?=?[[NSAutoreleasePool?alloc]?init];
//?put?object?into?pool
id?obj?=?[[NSObject?alloc]?init];
[obj?autorelease];
[pool?drain];
/*?超過(guò)autorelease?pool作用域范圍時(shí)薄扁,obj會(huì)自動(dòng)調(diào)用release方法?*/
引入ARC之后,寫法比之前更加簡(jiǎn)潔:
@autoreleasepool?{
????id?__autoreleasing?obj?=?[[NSObject?alloc]?init];
}相比之前的創(chuàng)建废累、使用和釋放NSAutoreleasePool對(duì)象邓梅,現(xiàn)在你只需要將代碼放在@autoreleasepool塊即可。你也不需要調(diào)用autorelease方法了邑滨,只需要用__autoreleasing修飾變量即可日缨。
有了ARC之后,新的property modifier也被引入到Objective-C類的property掖看,例如:
@property?(strong,?nonatomic)?NSString?*text;
下面有張表來(lái)展示property modifier與ownership qualifier的對(duì)應(yīng)關(guān)系
iOS 內(nèi)存泄漏監(jiān)測(cè)自動(dòng)化:http://www.cocoachina.com/ios/20170102/18490.html