前言
一般情況下,對象在超出作用域時會立即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
對象媚创。
如上圖所示渗钉,AutoreleasePool
在Runloop
中的創(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
銷毀自動釋放池术徊。
-
第一個
observe
的order
是-2147483647
,優(yōu)先級最高鲸湃,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前赠涮。
第二個Observer
的order
是2147483647
,優(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
中紫岩。