iOS內(nèi)存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解與管理

前言:上一篇內(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>
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ì)疑的地方,歡迎提出喲

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浮梢,隨后出現(xiàn)的幾起案子跛十,更是在濱河造成了極大的恐慌,老刑警劉巖秕硝,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芥映,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡远豺,警方通過(guò)查閱死者的電腦和手機(jī)奈偏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)躯护,“玉大人惊来,你說(shuō)我怎么就攤上這事」字停” “怎么了裁蚁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)继准。 經(jīng)常有香客問(wèn)我枉证,道長(zhǎng),這世上最難降的妖魔是什么移必? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任刽严,我火速辦了婚禮,結(jié)果婚禮上避凝,老公的妹妹穿的比我還像新娘。我一直安慰自己眨补,他們只是感情好管削,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著撑螺,像睡著了一般含思。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上甘晤,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天含潘,我揣著相機(jī)與錄音,去河邊找鬼线婚。 笑死遏弱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的塞弊。 我是一名探鬼主播漱逸,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼泪姨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了饰抒?” 一聲冷哼從身側(cè)響起肮砾,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袋坑,沒(méi)想到半個(gè)月后仗处,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枣宫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年婆誓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镶柱。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旷档,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歇拆,到底是詐尸還是另有隱情鞋屈,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布故觅,位于F島的核電站厂庇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏输吏。R本人自食惡果不足惜权旷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贯溅。 院中可真熱鬧拄氯,春花似錦、人聲如沸它浅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姐霍。三九已至鄙麦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間镊折,已是汗流浹背胯府。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恨胚,地道東北人骂因。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赃泡,于是被迫代替她去往敵國(guó)和親侣签。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塘装,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

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

  • 內(nèi)存管理 簡(jiǎn)述OC中內(nèi)存管理機(jī)制。與retain配對(duì)使用的方法是dealloc還是release影所,為什么蹦肴?需要與a...
    丶逐漸閱讀 1,965評(píng)論 1 16
  • 1. 內(nèi)總管理原則(引用計(jì)數(shù)) IOS的對(duì)象都繼承于NSObject, 該對(duì)象有一個(gè)方法:retainCount...
    lilinjianshu閱讀 2,160評(píng)論 0 2
  • 29.理解引用計(jì)數(shù) Objective-C語(yǔ)言使用引用計(jì)數(shù)來(lái)管理內(nèi)存,也就是說(shuō)猴娩,每個(gè)對(duì)象都有個(gè)可以遞增或遞減的計(jì)數(shù)...
    Code_Ninja閱讀 1,492評(píng)論 1 3
  • 喝酒和吸毒一樣卷中,會(huì)上癮的矛双。 古有詩(shī)仙李太白,斗酒詩(shī)百篇蟆豫,傳為美談议忽,又如桃花源里陶淵明,悠然見(jiàn)南山的灑脫和閑適十减,就連...
    雕琢人生閱讀 597評(píng)論 3 1
  • 第三章(重逢之際) 參觀完比賽場(chǎng)地后栈幸,大家就都分開(kāi)行動(dòng)了。小暉和方智東肩并肩的走在比賽場(chǎng)地的走廊里帮辟。這走廊里靜悄悄...
    我是可愛(ài)的尤利閱讀 1,619評(píng)論 0 1