iOS原理 AutoreleasePool的基本概念

iOS原理 文章匯總

前言

一般情況下,對象在超出作用域時會立即release保礼。比方說狸演,在一個方法里創(chuàng)建一個局部對象:

-(void)test{
    
    NSObject *obj = [[NSObject alloc] init];
}

當(dāng)test方法執(zhí)行完眉厨,這個NSObject對象就會被release了。但有些時候孵户,比如從工廠方法返回對象時萧朝,并不希望對象在超出作用域后立即release,這就需要通過AutoreleasePool來實(shí)現(xiàn)延届。

AutoreleasePool的基本介紹

AutoreleasePool(自動釋放池)是OC中的一種內(nèi)存管理機(jī)制剪勿,它持有釋放池里的對象的所有權(quán)贸诚,在自動釋放池銷毀時方庭,統(tǒng)一給所有對象發(fā)送一次release消息。通過這個機(jī)制酱固,可以延遲對象的釋放械念。

  • 延遲釋放
UIImage *img = [UIImage imageNamed:@"xxxx.png"];

這個UIImage對象是在類方法imageNamed里創(chuàng)建完后再返回,對象的所有權(quán)歸方法持有运悲,如果不延遲釋放龄减,在方法結(jié)束時對象就被釋放了,返回的就為nil班眯。因此希停,需要將對象先加入AutoreleasePool,所有權(quán)歸自動釋放池持有署隘,只有自動釋放池銷毀時才釋放宠能,這樣UIImage對象才能在方法結(jié)束后正常返回。

AutoreleasePool的創(chuàng)建方式

通常使用@autoreleasepool {}代碼塊來手動創(chuàng)建一個自動釋放池

@autoreleasepool {
    //這里創(chuàng)建自動釋放的對象磁餐,創(chuàng)建的對象會被加入到AutoreleasePool對象里
    ... ...
}

這個代碼塊等價于

{
    //創(chuàng)建一個AutoreleasePool對象
    __AtAutoreleasePool *atautoreleasepoolobj = objc_autoreleasePoolPush(); 
    
    //這里創(chuàng)建自動釋放的對象违崇,創(chuàng)建的對象會被加入到AutoreleasePool對象里
    ... ...    

   //給所有自動釋放的對象發(fā)送一次release消息,并銷毀AutoreleasePool對象
   objc_autoreleasePoolPop(atautoreleasepoolobj)
}

`{}`表示AutoreleasePool對象的作用域

代碼塊的實(shí)現(xiàn)邏輯如下:

  • 先通過調(diào)用objc_autoreleasePoolPush函數(shù)來創(chuàng)建一個AutoreleasePool對象诊霹。
  • 然后給在代碼塊里創(chuàng)建的每個自動釋放的對象發(fā)送一個autorelease消息羞延,將這些自動釋放的對象加入到AutoreleasePool對象里。
  • 最后在AutoreleasePool對象將要銷毀時脾还,通過調(diào)用objc_autoreleasePoolPop函數(shù)給池中每個自動釋放的對象發(fā)送一次release消息伴箩,再銷毀AutoreleasePool對象。

注意區(qū)分AutoreleasePool對象自動釋放的對象鄙漏,AutoreleasePool對象指的是實(shí)例化的一個自動釋放池(本質(zhì)也是對象)嗤谚,而 自動釋放的對象是指被加入到這個池中的對象。
AutoreleasePool的原理可閱讀后面的底層分析一文泥张。

AutoreleasePool在Runloop中的創(chuàng)建和銷毀

通常情況下呵恢,在平時開發(fā)中不需要手動創(chuàng)建自動釋放池,因?yàn)?code>Runloop會自動創(chuàng)建和銷毀AutoreleasePool對象媚创。

如上圖所示渗钉,AutoreleasePoolRunloop中的創(chuàng)建和銷毀的過程如下:

  • App啟動后,系統(tǒng)在主線程RunLoop里注冊了兩個Observer,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()鳄橘。

  • 第一個Observer監(jiān)視一個事件:

    • Entry(即將進(jìn)入Loop):調(diào)用objc_autoreleasePoolPush來創(chuàng)建自動釋放池声离。
  • 第二個Observer監(jiān)視了兩個事件:

    • Before waiting(準(zhǔn)備進(jìn)入休眠):先調(diào)用objc_autoreleasePoolPop銷毀舊的自動釋放池,再調(diào)用objc_autoreleasePoolPush創(chuàng)建一個新的自動釋放池瘫怜。
    • Exit(即將退出Loop):調(diào)用objc_autoreleasePoolPop銷毀自動釋放池术徊。
  • 第一個observeorder-2147483647,優(yōu)先級最高鲸湃,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前赠涮。
    第二個Observerorder2147483647,優(yōu)先級最低暗挑,保證銷毀自動釋放池發(fā)生在其他所有回調(diào)之后笋除。

也就是說,在一個RunLoop事件開始的時候會自動創(chuàng)建一個AutoreleasePool炸裆,在事件結(jié)束時再自動銷毀垃它。上面舉例的imageNamed方法內(nèi)部創(chuàng)建的對象也是加入到主線程RunLoop創(chuàng)建的AutoreleasePool中實(shí)現(xiàn)延遲釋放的。因此烹看,通常在開發(fā)中不需要開發(fā)者自己創(chuàng)建AutoreleasePool国拇。

手動創(chuàng)建AutoreleasePool的場景

雖然Runloop會自動創(chuàng)建和銷毀自動釋放池,但在有些情況下還是需要手動創(chuàng)建AutoreleasePool惯殊。蘋果官方文檔建議在下面這三種情況下可能需要開發(fā)者創(chuàng)建自動釋放池:

  • 編寫不基于UI框架的程序酱吝,例如命令行工具。

    這一點(diǎn)的原因不是特別清楚靠胜,猜測是不基于UI框架的程序掉瞳,可能不響應(yīng)用戶事件,導(dǎo)致不自動創(chuàng)建和銷毀自動釋放池浪漠。

  • 編寫一個創(chuàng)建大量臨時對象的循環(huán)陕习。

    在循環(huán)內(nèi)使用自動釋放池塊可以在下一次迭代之前釋放這些對象,有助于減少應(yīng)用程序的最大內(nèi)存占用址愿,即降低內(nèi)存峰值该镣。

  • 編寫非Cocoa程序時創(chuàng)建子線程。

    Cocoa程序中的每個線程都維護(hù)自己的自動釋放池塊堆棧响谓。而編寫一個非Cocoa程序损合,比如Foundation-only program,這時如果創(chuàng)建了子線程娘纷,若不手動創(chuàng)建自動釋放池嫁审,自動釋放的對象將會堆積得不到釋放,導(dǎo)致內(nèi)存泄漏赖晶。

這里就第二個場景舉例律适,來說明在循環(huán)內(nèi)使用AutoreleasePool對于降低內(nèi)存峰值的作用辐烂。

//情況一:循環(huán)內(nèi)不使用AutoreleasePool
for (int i = 0; i<1000000; i++) {

    NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
    NSLog(@" ==== %p", string);
}

//情況二:循環(huán)內(nèi)使用AutoreleasePool
for (int i = 0; i<1000000; i++) {

    @autoreleasepool {

        NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
        NSLog(@" ==== %p", string);
    }
}

分別運(yùn)行上面兩種情況可以看到,在循環(huán)過程中捂贿,第一種情況的內(nèi)存占用一直在增加纠修,第二種情況的內(nèi)存不會增加。這是因?yàn)椋?/p>

  • 情況一:循環(huán)過程中厂僧,創(chuàng)建的NSString對象一直在堆積扣草,只有在循環(huán)結(jié)束才一起釋放,所以內(nèi)存一直在增加颜屠。
  • 情況二:每一次迭代中都會創(chuàng)建并銷毀一個AutoreleasePool辰妙,而每一次創(chuàng)建的NSString對象都會加入到AutoreleasePool中,所以在每次AutoreleasePool銷毀時汽纤,NSString對象就會被釋放上岗,這樣內(nèi)存就不會增加。

這個場景中AutoreleasePool是通過立即釋放對象來降低內(nèi)存峰值蕴坪,而前面又說自動釋放池用來延遲對象的釋放,這兩者其實(shí)不矛盾敬锐,本質(zhì)是一樣的背传,都是在自動釋放池銷毀時調(diào)用objc_autoreleasePoolPop來釋放池中的對象。只不過調(diào)用的時機(jī)不同台夺,這里的@autoreleasepool {}是在超出自己的作用域時就調(diào)用函數(shù)來銷毀径玖,而前面的是在Runloop休眠或退出時才調(diào)用函數(shù)來銷毀,所以調(diào)用的時機(jī)不同颤介,才會實(shí)現(xiàn)立即或者延遲釋放的目的梳星。

@autoreleasepool {}的作用域指的就是前面提到的{},是AutoreleasePool對象的作用域滚朵。

哪些對象可以被添加到自動釋放池冤灾?

MRC模式下,只要給對象發(fā)送autorelease消息辕近,這個對象就會被添加到自動釋放池韵吨。但在ARC模式下,是由編譯器自動給對象發(fā)送autorelease消息移宅,且不會給所有的對象都發(fā)送归粉,只會給被編譯器識別為自動釋放的對象發(fā)送。一般來說漏峰,使用類方法(工廠方法)實(shí)例化的對象才是自動釋放的對象糠悼,才能被添加到自動釋放池,而使用new浅乔、alloc倔喂、copy關(guān)鍵字生成的對象和retain了的對象,不會被添加到自動釋放池中。

  • UIImage對象為例
for (int i = 0; i<1000000; i++) {

    @autoreleasepool {

        //1.自動釋放的對象滴劲,需要被添加到自動釋放池中
        UIImage *image = [UIImage imageNamed:@"test.png"];
        
        //2.非自動釋放的對象攻晒,不能被添加到自動釋放池中
        UIImage *image = [[UIImage alloc] init];
        
        NSLog(@" ==== image = %p", image);
    }
}

分別運(yùn)行上面兩種情況,第一種情況內(nèi)存不會增加班挖,第二種情況內(nèi)存會增加鲁捏。第二種情況雖然在@autoreleasepool {}中創(chuàng)建對象,但由于不是自動釋放的對象萧芙,所以還是不能被添加到AutoReleasePool中给梅,只能在循環(huán)結(jié)束一起釋放。因此双揪,在ARC模式下动羽,只有自動釋放的對象才能被添加到AutoReleasePool中,非自動釋放的對象在超出作用域時會被立即釋放渔期。

需要注意的是运吓,自動釋放的對象如果沒有被添加到AutoReleasePool中,就會產(chǎn)生內(nèi)存泄露疯趟。

總結(jié)

總得來說拘哨,關(guān)于AutoreleasePool的基本概念可以歸納以下幾點(diǎn):

  • AutoreleasePool(自動釋放池)持有釋放池里的對象的所有權(quán),在自動釋放池銷毀時信峻,統(tǒng)一給所有對象發(fā)送一次release消息倦青。
  • 在一個RunLoop事件開始的時候會自動創(chuàng)建一個AutoreleasePool,在事件結(jié)束時再自動銷毀产镐,這樣可以延遲對象的release踢步。
  • 也可以使用@autoreleasepool {}來手動創(chuàng)建自動釋放池逃糟,在循環(huán)中使用可以立即釋放對象,降低內(nèi)存峰值。
  • MRC模式下玩敏,只要給對象發(fā)送autorelease消息织阳,這個對象就會被添加到自動釋放池唧躲。
  • ARC模式下,一般來說,使用類方法(工廠方法)實(shí)例化的對象才是自動釋放的對象审姓,才能被添加到自動釋放池凭疮。自動釋放的對象如果沒有被添加到AutoReleasePool中寞肖,就會產(chǎn)生內(nèi)存泄露觅赊。
  • ARC中,自動釋放的對象由編譯器自主識別并發(fā)送autorelease消息,添加到AutoReleasePool中紫岩。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贪磺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壕曼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異过咬,居然都是意外死亡耕捞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門弛饭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孩哑,你說我怎么就攤上這事栓霜。” “怎么了横蜒?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵胳蛮,是天一觀的道長销凑。 經(jīng)常有香客問我,道長仅炊,這世上最難降的妖魔是什么斗幼? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮抚垄,結(jié)果婚禮上蜕窿,老公的妹妹穿的比我還像新娘。我一直安慰自己呆馁,他們只是感情好桐经,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浙滤,像睡著了一般阴挣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纺腊,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天畔咧,我揣著相機(jī)與錄音,去河邊找鬼揖膜。 笑死誓沸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的壹粟。 我是一名探鬼主播拜隧,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼趁仙!你這毒婦竟也來了虹蓄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤幸撕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后外臂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坐儿,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年宋光,在試婚紗的時候發(fā)現(xiàn)自己被綠了貌矿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡罪佳,死狀恐怖逛漫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赘艳,我是刑警寧澤酌毡,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布克握,位于F島的核電站,受9級特大地震影響枷踏,放射性物質(zhì)發(fā)生泄漏菩暗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一旭蠕、第九天 我趴在偏房一處隱蔽的房頂上張望停团。 院中可真熱鬧,春花似錦掏熬、人聲如沸佑稠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舌胶。三九已至,卻和暖如春岗屏,著一層夾襖步出監(jiān)牢的瞬間辆琅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工这刷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婉烟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓暇屋,卻偏偏與公主長得像似袁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咐刨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容