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


推荐阅读
  • iOS如何实现手势
    这篇文章主要为大家展示了“iOS如何实现手势”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“iOS ... [详细]
  • 使用 ModelAttribute 实现页面数据自动填充
    本文介绍了如何利用 Spring MVC 中的 ModelAttribute 注解,在页面跳转后自动填充表单数据。主要探讨了两种实现方法及其背后的原理。 ... [详细]
  • egg实现登录鉴权(七):权限管理
    权限管理包含三部分:访问页面的权限,操作功能的权限和获取数据权限。页面权限:登录用户所属角色的可访问页面的权限功能权限:登录用户所属角色的可访问页面的操作权限数据权限:登录用户所属 ... [详细]
  • 来自FallDream的博客,未经允许,请勿转载,谢谢。一天一套noi简直了.昨天勉强做完了noi2011今天教练又丢出来一套noi ... [详细]
  • Lua字符串1.字符串常见形式字符串或串(String)是由数字、字母、下划线组成的一串字符。Lua语言中字符串可以使用以下三种方式来表示:•单引号间的一串字符。 ... [详细]
  • 本文介绍了如何使用Java编程语言实现凯撒密码的加密与解密功能。凯撒密码是一种替换式密码,通过将字母表中的每个字母向前或向后移动固定数量的位置来实现加密。 ... [详细]
  • 本文介绍如何使用 Python 计算两个时间戳之间的时间差,并将其转换为毫秒。示例代码展示了如何通过 `time` 和 `datetime` 模块实现这一功能。 ... [详细]
  • java datarow_DataSet  DataTable DataRow 深入浅出
    本篇文章适合有一定的基础的人去查看,最好学习过一定net编程基础在来查看此文章。1.概念DataSet是ADO.NET的中心概念。可以把DataSet当成内存中的数据 ... [详细]
  • 题面:P3178[HAOI2015]树上操作好像其他人都嫌这道题太容易了懒得讲,好吧那我讲。题解:第一个操作和第二个操作本质上是一样的&# ... [详细]
  • 探索CNN的可视化技术
    神经网络的可视化在理论学习与实践应用中扮演着至关重要的角色。本文深入探讨了三种有效的CNN(卷积神经网络)可视化方法,旨在帮助读者更好地理解和优化模型。 ... [详细]
  • 一.数据基本类型之set集合set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在se ... [详细]
  • 使用Java计算两个日期之间的月份数
    本文详细介绍了利用Java编程语言计算两个指定日期之间月份数的方法。文章通过实例代码讲解了如何使用Joda-Time库来简化日期处理过程,旨在为开发者提供一个高效且易于理解的解决方案。 ... [详细]
  • Hadoop MapReduce 实战案例:手机流量使用统计分析
    本文通过一个具体的Hadoop MapReduce案例,详细介绍了如何利用MapReduce框架来统计和分析手机用户的流量使用情况,包括上行和下行流量的计算以及总流量的汇总。 ... [详细]
  • Java连接MySQL数据库的方法及测试示例
    本文详细介绍了如何安装MySQL数据库,并通过Java编程语言实现与MySQL数据库的连接,包括环境搭建、数据库创建以及简单的查询操作。 ... [详细]
  • 题目概述:Sereja 拥有一个由 n 个整数组成的数组 a1, a2, ..., an。他计划执行 m 项操作,这些操作包括更新数组中的特定元素、增加数组中所有元素的值,以及查询数组中的特定元素。 ... [详细]
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社区 版权所有