iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解与管理

前言:上一篇内存管理里面, iOS内存管理篇(一)--alloc/reatain/release/dealloc方法实现 我们提到了如何引用计数的概念,那么今天我们来看看 NSAuoreleasePool是什么,如何工作的的,又是一个怎样的原理。

NSAutoreleasePool是什么

  • 官方释义:NSAutoreleasePool 是 Cocoa 用来支持引用计数内存管理机制的类, 当一个autorelease pool(自动释放池)被drain(销毁)的时候会对pool里的对象发送一条release的消息.

  • 个人理解:NSAutoreleasePool是一个对象池,它管理着在池内的对象的引用计数以及何时销毁问题。

那么现在有朋友会说,NSAutoreleasePool离我们很远啊,从来没有使用过,是的,NSAutoreleasePool 是在 MRC时代使用的,那么 ARC是使用什么呢

@autoreleasepool {
    }

PS:使用以上的代码的时候,系统自动为我们创建了一个 NSAutoreleasePool
那么来说一个离我们最近的@autoreleasepool吧,我们新建一个工程,然后可以看到如下图的 main.m 文件

<center>
main.m

</center>

打开 main.m 文件,我们可以看到如下代码

@autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }

原来在我们工程创建的时候,系统就为我们创建好了一个@autoreleasepool。
那么来讲一下这个@autoreleasepool吧。


一个项目里面可以有多个@autoreleasepool

每一个 NSRunLoop会隐式创建一个autoreleasepool
新建一个@autoreleasepool会像堆栈一样压入@autoreleasepool组里面,新的@autoreleasepool会代替当前的@autoreleasepool成为新的当前@autoreleasepool。当每一个NSRunLoop结束的时候,会将当前的autoreleasepool进行销毁,如下的一个结构图

这里写图片描述

PS: 可以把autorelease pool理解成一个类似父类与子类的关系,main()创建了父类,每个Runloop自动生成的或者开发者自定义的autorelease pool都会成为该父类的子类。当父类被释放的时候,没有被释放的子类也会被释放,这样所有子类中的对象也会收到release消息。

我们来看看实际的一个例子

有如下的代码:

#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是一个autorelease对象,设置一个weak的引用来观察它
    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);

打印结果如下

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 

以上结果说明这三个方法都是在一个 autorelease实现的,我们也可以手动修改作用块

- (void)viewDidLoad {
    [super viewDidLoad];
    __autoreleasing NSString *str;
    @autoreleasepool {
        str = [NSString stringWithFormat:@"sunnyxx"];
    }
    NSLog(@"%@", str); // Console: (null)
}

关于__autoreleaseing 的解释是

__autoreleasing表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。

当我们创建一个 autorelease pool 的时候,系统是如何做的呢,系统会生成一个叫做“autorelease pool page”的东西,为我们开辟一页的虚拟内存空间,至于这个类是怎么实现的借助一下这篇文章的一个图片黑幕背后的Autorelease

这里写图片描述

我们知道内存地址的分配都是由低地址分配到高地址,最开始栈顶指针和栈底指针是一致的, 随着我们往当前的autoreleasepool里面增加元素栈顶地址也会增加,每释放一个元素,栈顶地址也会随之下降,如果是直接释放整个 autoreleasepool的话,里面的元素也会随之释放。
嵌套式的 autoleasepool 也是如此。

理解 autorelease

Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release

举个例子来说,有如下代码

-(void)viewDidLoad
{
    [super viewDidLoad];
    Person *p = [[Person alloc]init];
    [p release];
    p.name = @"I am Lili";
}

这个时候,带么执行到"p.name = @"I am Lili";"这一句的时候就会报错,原因很简单,因为 p 已经被释放了,这个内存地址已经不存在了,而再次调用 p.name = @"I am Lili";,就会产生野指针

在 ARC的模式下,我们不需要手动调用 release 方法,系统在编译阶段自动为我们加上了释放的代码

例如: 有如下代码

+ (instancetype)createSark {
    return [self new];
}
// caller
Sark *sark = [Sark createSark];

系统在编译阶段创建的代码是这样的

+ (instancetype)createSark {
    return [[self new]autorelease];
}
// caller
Sark *sark = [[Sark createSark]autorelease];

什么样的场景下用autoreleasepool?
苹果官方是这么说的

  • If you are writing a program that is not based on a UI framework, such as a command-line tool.
    你写的程序不是基于UI framework, 例如命令行项目

  • 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.
    你写的循环创建了大量临时对象 -> 你需要在循环体内创建一个autorelease pool block并且在每次循环结束之前处理那些autoreleased对象. 在循环中使用autorelease pool block可以降低内存峰值

  • You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.
    你创建了一个新线程
    当线程开始执行的时候你必须立马创建一个autorelease pool block, 否则你的应用会造成内存泄露.

举个例子来说

+ (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;
}

如果循环几百次调用以上的代码,就会收到内存警告,如何优化,代码如下:

+ (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 后就不会收到内存警告了 arc 模式下使用@autoreleasepool

平时使用 for 循环和 for in 循环,苹果官方还给我们提供了一种循环遍历的方法,叫做
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
在内存上也进行了优化,有兴趣的同学可以试一试。

在使用的时候需要注意什么

  • 在ARC项目中我们同样可以创建NSAutoreleasePool类对象去帮助我们更精确的管理内存问题。

  • NSAutoreleasePool的管理范围是在NSAutoreleasePool *pool =
    [[NSAutoreleasePool alloc]init];与[pool release];之间的对象

  • 既然ARC项目中设置了ARC,为什么还要使用@autoreleasepool?(注意a的案例解释)ARC 并不是舍弃了
    @autoreleasepool,而是在编译阶段帮你插入必要的 retain/release/autorelease
    的代码调用。所以,跟你想象的不一样,ARC 之下依然是延时释放的,依然是依赖于 NSAutoreleasePool,跟非 ARC
    模式下手动调用那些函数本质上毫无差别,只是编译器来做会保证引用计数的正确性

  • NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; 当执行[pool
    autorelease]的时候,系统会进行一次内存释放,把autorelease的对象释放掉,如果没有NSAutoreleasePool
    , 那这些内存不会释放
    注意,对象并不是自动被加入到当前pool中,而是需要对对象发送autorelease消息,这样,对象就被加到当前pool的管理里了。当当前pool接受到drain消息时,它就简单的对它所管理的所有对象发送release消息。

  • 在ARC项目中.不能直接使用autorelease pools,而是使用@autoreleasepool{},
    @autoreleasepool{}比直接使用NSAutoreleasePool效率高。不使用ARC的时候也可以使用(autorelease嵌套)

好了,我们说了这么多,了解了 NSAutoreleasePool 和 autorelease 的概念,上一篇文章也学习了 alloc/reatain/release/dealloc的使用方法,其实都是内存管理的一些基础知识,系统是如何为我们分配内存的,如何管理对象引用计数的,如何在适当的时候给我们添加代码的,都有做详细的说明,大家有不同或者对本文有质疑的地方,欢迎提出哟

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,864评论 1 16
  • 1. 内总管理原则(引用计数) IOS的对象都继承于NSObject, 该对象有一个方法:retainCount...
    lilinjianshu阅读 2,120评论 0 2
  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,420评论 1 3
  • 喝酒和吸毒一样,会上瘾的。 古有诗仙李太白,斗酒诗百篇,传为美谈,又如桃花源里陶渊明,悠然见南山的洒脱和闲适,就连...
    雕琢人生阅读 551评论 3 1
  • 第三章(重逢之际) 参观完比赛场地后,大家就都分开行动了。小晖和方智东肩并肩的走在比赛场地的走廊里。这走廊里静悄悄...
    我是可爱的尤利阅读 1,493评论 0 1