前言:上一篇內(nèi)存管理里面, iOS內(nèi)存管理篇(一)--alloc/reatain/release/dealloc方法實(shí)現(xiàn) 我們提到了如何引用計(jì)數(shù)的概念毙替,那么今天我們來(lái)看看 NSAuoreleasePool是什么岸售,如何工作的的,又是一個(gè)怎樣的原理厂画。
NSAutoreleasePool是什么
官方釋義:NSAutoreleasePool 是 Cocoa 用來(lái)支持引用計(jì)數(shù)內(nèi)存管理機(jī)制的類, 當(dāng)一個(gè)autorelease pool(自動(dòng)釋放池)被drain(銷毀)的時(shí)候會(huì)對(duì)pool里的對(duì)象發(fā)送一條release的消息.
個(gè)人理解:NSAutoreleasePool是一個(gè)對(duì)象池凸丸,它管理著在池內(nèi)的對(duì)象的引用計(jì)數(shù)以及何時(shí)銷毀問(wèn)題。
那么現(xiàn)在有朋友會(huì)說(shuō)袱院,NSAutoreleasePool離我們很遠(yuǎn)啊甲雅,從來(lái)沒(méi)有使用過(guò),是的坑填,NSAutoreleasePool 是在 MRC時(shí)代使用的,那么 ARC是使用什么呢
@autoreleasepool {
}
PS:使用以上的代碼的時(shí)候弛姜,系統(tǒng)自動(dòng)為我們創(chuàng)建了一個(gè) NSAutoreleasePool
那么來(lái)說(shuō)一個(gè)離我們最近的@autoreleasepool吧脐瑰,我們新建一個(gè)工程,然后可以看到如下圖的 main.m 文件
</center>
打開(kāi) main.m 文件,我們可以看到如下代碼
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
原來(lái)在我們工程創(chuàng)建的時(shí)候廷臼,系統(tǒng)就為我們創(chuàng)建好了一個(gè)@autoreleasepool苍在。
那么來(lái)講一下這個(gè)@autoreleasepool吧。
一個(gè)項(xiàng)目里面可以有多個(gè)@autoreleasepool
每一個(gè) NSRunLoop會(huì)隱式創(chuàng)建一個(gè)autoreleasepool
新建一個(gè)@autoreleasepool會(huì)像堆棧一樣壓入@autoreleasepool組里面荠商,新的@autoreleasepool會(huì)代替當(dāng)前的@autoreleasepool成為新的當(dāng)前@autoreleasepool寂恬。當(dāng)每一個(gè)NSRunLoop結(jié)束的時(shí)候,會(huì)將當(dāng)前的autoreleasepool進(jìn)行銷毀莱没,如下的一個(gè)結(jié)構(gòu)圖
PS: 可以把a(bǔ)utorelease pool理解成一個(gè)類似父類與子類的關(guān)系初肉,main()創(chuàng)建了父類,每個(gè)Runloop自動(dòng)生成的或者開(kāi)發(fā)者自定義的autorelease pool都會(huì)成為該父類的子類饰躲。當(dāng)父類被釋放的時(shí)候牙咏,沒(méi)有被釋放的子類也會(huì)被釋放,這樣所有子類中的對(duì)象也會(huì)收到release消息嘹裂。
我們來(lái)看看實(shí)際的一個(gè)例子
有如下的代碼:
#import "MStestaaaViewController.h"
@interface MStestaaaViewController ()
@property (nonatomic ,copy) NSString *testStr;
@end
@implementation MStestaaaViewController
__weak id reference = nil;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = [NSString stringWithFormat:@"I am a test"];
// str是一個(gè)autorelease對(duì)象妄壶,設(shè)置一個(gè)weak的引用來(lái)觀察它
reference = str;
NSLog(@"viewDidLoad with testStr = %@",reference);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"viewWillAppear with testStr = %@",reference);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear with testStr = %@",reference);
打印結(jié)果如下
2017-07-13 20:36:15.541 hi7_client[4185:603299] viewDidLoad with testStr = I am a test
2017-07-13 20:36:15.544 hi7_client[4185:603299] viewWillAppear with testStr = I am a test
2017-07-13 20:36:37.466 hi7_client[4185:603299] viewDidDisappear with testStr = I am a test
2017-07-13 20:36:37.467 hi7_client[4185:603299] dealloc
以上結(jié)果說(shuō)明這三個(gè)方法都是在一個(gè) autorelease實(shí)現(xiàn)的,我們也可以手動(dòng)修改作用塊
- (void)viewDidLoad {
[super viewDidLoad];
__autoreleasing NSString *str;
@autoreleasepool {
str = [NSString stringWithFormat:@"sunnyxx"];
}
NSLog(@"%@", str); // Console: (null)
}
關(guān)于__autoreleaseing 的解釋是
__autoreleasing表示在autorelease pool中自動(dòng)釋放對(duì)象的引用,和MRC時(shí)代autorelease的用法相同寄狼。定義property時(shí)不能使用這個(gè)修飾符丁寄,任何一個(gè)對(duì)象的property都不應(yīng)該是autorelease型的。
當(dāng)我們創(chuàng)建一個(gè) autorelease pool 的時(shí)候,系統(tǒng)是如何做的呢伊磺,系統(tǒng)會(huì)生成一個(gè)叫做“autorelease pool page”的東西盛正,為我們開(kāi)辟一頁(yè)的虛擬內(nèi)存空間,至于這個(gè)類是怎么實(shí)現(xiàn)的借助一下這篇文章的一個(gè)圖片黑幕背后的Autorelease
我們知道內(nèi)存地址的分配都是由低地址分配到高地址奢浑,最開(kāi)始棧頂指針和棧底指針是一致的蛮艰, 隨著我們往當(dāng)前的autoreleasepool里面增加元素棧頂?shù)刂芬矔?huì)增加,每釋放一個(gè)元素雀彼,棧頂?shù)刂芬矔?huì)隨之下降壤蚜,如果是直接釋放整個(gè) autoreleasepool的話,里面的元素也會(huì)隨之釋放徊哑。
嵌套式的 autoleasepool 也是如此袜刷。
理解 autorelease
Autorelease實(shí)際上只是把對(duì)release的調(diào)用延遲了,對(duì)于每一個(gè)Autorelease莺丑,系統(tǒng)只是把該Object放入了當(dāng)前的Autorelease pool中著蟹,當(dāng)該pool被釋放時(shí),該pool中的所有Object會(huì)被調(diào)用Release
舉個(gè)例子來(lái)說(shuō)梢莽,有如下代碼
-(void)viewDidLoad
{
[super viewDidLoad];
Person *p = [[Person alloc]init];
[p release];
p.name = @"I am Lili";
}
這個(gè)時(shí)候,帶么執(zhí)行到"p.name = @"I am Lili";"這一句的時(shí)候就會(huì)報(bào)錯(cuò),原因很簡(jiǎn)單,因?yàn)?p 已經(jīng)被釋放了,這個(gè)內(nèi)存地址已經(jīng)不存在了,而再次調(diào)用 p.name = @"I am Lili";,就會(huì)產(chǎn)生野指針
在 ARC的模式下,我們不需要手動(dòng)調(diào)用 release 方法,系統(tǒng)在編譯階段自動(dòng)為我們加上了釋放的代碼
例如: 有如下代碼
+ (instancetype)createSark {
return [self new];
}
// caller
Sark *sark = [Sark createSark];
系統(tǒng)在編譯階段創(chuàng)建的代碼是這樣的
+ (instancetype)createSark {
return [[self new]autorelease];
}
// caller
Sark *sark = [[Sark createSark]autorelease];
什么樣的場(chǎng)景下用autoreleasepool?
蘋果官方是這么說(shuō)的
- If you are writing a program that is not based on a UI framework, such as a command-line tool.
你寫的程序不是基于UI framework, 例如命令行項(xiàng)目
- If you write a loop that creates many temporary objects.
You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.
If you spawn a secondary thread.
你寫的循環(huán)創(chuàng)建了大量臨時(shí)對(duì)象 -> 你需要在循環(huán)體內(nèi)創(chuàng)建一個(gè)autorelease pool block并且在每次循環(huán)結(jié)束之前處理那些autoreleased對(duì)象. 在循環(huán)中使用autorelease pool block可以降低內(nèi)存峰值
- You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.
你創(chuàng)建了一個(gè)新線程
當(dāng)線程開(kāi)始執(zhí)行的時(shí)候你必須立馬創(chuàng)建一個(gè)autorelease pool block, 否則你的應(yīng)用會(huì)造成內(nèi)存泄露.
舉個(gè)例子來(lái)說(shuō)
+ (UIImage*)simpleImageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
// Create a graphics image context
UIGraphicsBeginImageContext(newSize);
// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
// Return the new image.
return newImage;
}
如果循環(huán)幾百次調(diào)用以上的代碼,就會(huì)收到內(nèi)存警告,如何優(yōu)化,代碼如下:
+ (UIImage*)simpleImageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
//http://wiresareobsolete.com/2010/08/uiimagepickercontroller/
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Create a graphics image context
UIGraphicsBeginImageContext(newSize);
// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
[newImage retain];
[pool release];
// Return the new image.
return newImage;
}
添加上了 nsautoreleasepool 后就不會(huì)收到內(nèi)存警告了 arc 模式下使用@autoreleasepool
平時(shí)使用 for 循環(huán)和 for in 循環(huán),蘋果官方還給我們提供了一種循環(huán)遍歷的方法,叫做
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這里被一個(gè)局部@autoreleasepool包圍著
}];
在內(nèi)存上也進(jìn)行了優(yōu)化,有興趣的同學(xué)可以試一試萧豆。
在使用的時(shí)候需要注意什么
- 在ARC項(xiàng)目中我們同樣可以創(chuàng)建NSAutoreleasePool類對(duì)象去幫助我們更精確的管理內(nèi)存問(wèn)題。
- NSAutoreleasePool的管理范圍是在NSAutoreleasePool *pool =
[[NSAutoreleasePool alloc]init];與[pool release];之間的對(duì)象
- 既然ARC項(xiàng)目中設(shè)置了ARC昏名,為什么還要使用@autoreleasepool?(注意a的案例解釋)ARC 并不是舍棄了
@autoreleasepool涮雷,而是在編譯階段幫你插入必要的 retain/release/autorelease
的代碼調(diào)用。所以轻局,跟你想象的不一樣洪鸭,ARC 之下依然是延時(shí)釋放的,依然是依賴于 NSAutoreleasePool仑扑,跟非 ARC
模式下手動(dòng)調(diào)用那些函數(shù)本質(zhì)上毫無(wú)差別览爵,只是編譯器來(lái)做會(huì)保證引用計(jì)數(shù)的正確性
- NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; 當(dāng)執(zhí)行[pool
autorelease]的時(shí)候,系統(tǒng)會(huì)進(jìn)行一次內(nèi)存釋放镇饮,把a(bǔ)utorelease的對(duì)象釋放掉蜓竹,如果沒(méi)有NSAutoreleasePool
, 那這些內(nèi)存不會(huì)釋放
注意,對(duì)象并不是自動(dòng)被加入到當(dāng)前pool中盒让,而是需要對(duì)對(duì)象發(fā)送autorelease消息梅肤,這樣,對(duì)象就被加到當(dāng)前pool的管理里了邑茄。當(dāng)當(dāng)前pool接受到drain消息時(shí)姨蝴,它就簡(jiǎn)單的對(duì)它所管理的所有對(duì)象發(fā)送release消息。
- 在ARC項(xiàng)目中.不能直接使用autorelease pools,而是使用@autoreleasepool{},
@autoreleasepool{}比直接使用NSAutoreleasePool效率高肺缕。不使用ARC的時(shí)候也可以使用(autorelease嵌套)
好了左医,我們說(shuō)了這么多授帕,了解了 NSAutoreleasePool 和 autorelease 的概念,上一篇文章也學(xué)習(xí)了 alloc/reatain/release/dealloc的使用方法,其實(shí)都是內(nèi)存管理的一些基礎(chǔ)知識(shí),系統(tǒng)是如何為我們分配內(nèi)存的,如何管理對(duì)象引用計(jì)數(shù)的,如何在適當(dāng)?shù)臅r(shí)候給我們添加代碼的,都有做詳細(xì)的說(shuō)明,大家有不同或者對(duì)本文有質(zhì)疑的地方,歡迎提出喲