热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

前言:上一篇内存管理里面,iOS内存管理篇(一)–allocreatainreleasedealloc方法实现我们提到了如何引用计数的概念,那么今天我们来看看NSAuoreleas

前言:上一篇内存管理里面, 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 文件

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

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

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

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

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

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

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

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

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

有如下的代码:

#import "MStest#ViewController.h"
@interface MStest#ViewController ()
@property (nonatomic ,copy) NSString *testStr;
@end
@implementation MStest#ViewController
__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

《iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/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的使用方法,其实都是内存管理的一些基础知识,系统是如何为我们分配内存的,如何管理对象引用计数的,如何在适当的时候给我们添加代码的,都有做详细的说明,大家有不同或者对本文有质疑的地方,欢迎提出哟


推荐阅读
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • ScrollView嵌套Collectionview无痕衔接四向滚动,支持自定义TitleView
    本文介绍了如何实现ScrollView嵌套Collectionview无痕衔接四向滚动,并支持自定义TitleView。通过使用MainScrollView作为最底层,headView作为上部分,TitleView作为中间部分,Collectionview作为下面部分,实现了滚动效果。同时还介绍了使用runtime拦截_notifyDidScroll方法来实现滚动代理的方法。具体实现代码可以在github地址中找到。 ... [详细]
  • “你永远都不知道明天和‘公司的意外’哪个先来。”疫情期间,这是我们最战战兢兢的心情。但是显然,有些人体会不了。这份行业数据,让笔者“柠檬” ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • GPT-3发布,动动手指就能自动生成代码的神器来了!
    近日,OpenAI发布了最新的NLP模型GPT-3,该模型在GitHub趋势榜上名列前茅。GPT-3使用的数据集容量达到45TB,参数个数高达1750亿,训练好的模型需要700G的硬盘空间来存储。一位开发者根据GPT-3模型上线了一个名为debuid的网站,用户只需用英语描述需求,前端代码就能自动生成。这个神奇的功能让许多程序员感到惊讶。去年,OpenAI在与世界冠军OG战队的表演赛中展示了他们的强化学习模型,在限定条件下以2:0完胜人类冠军。 ... [详细]
author-avatar
万世一统_425
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有