转自其他

序言

  • 无论是在MRC时期还是ARC时期,做过开发的程序员都接触过autoreleasepool。尽管接触过但本人对它还不是很了解。本文只是将自己的理解说出来。在内存管理的文章中提到了OC的内存管理是通过引用计数来完成的,也介绍了可以通过内存管理的方法(alloc/retain/new/copy等)来使引用计数加1,使用release方法来使引用计数减1。在我们创建了大量对象的时候,如果还是手动调用release方法来释放它们就显得太繁琐了。本文章将介绍内存管理的另外一种机制-autoreleasepool。

autoreleasepool概念

  • 自动释放池是NSAutoreleasePool的实例,其中包含了收到autorelease消息的对象。当一个自动释放池自身被销毁(dealloc)时,它会给池中每一个对象发送一个release消息(如果你给一个对象多次发送autorelease消息,那么当自动释放池销毁时,这个对象也会收到同样数目的release消息)。可以看出,一个自动释放的对象,它至少能够存活到自动释放池销毁的时候。这样看来它是一种延迟释放机制,这样保证局部堆上的变量能够被外部正常使用。

  • 这里说一下,在Xcode5以前是通过NSAutoreleasePool创建实例来实现的,代码如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// code
[pool drain];
  • 但是在Xcode5以后,它的写法就简单了,代码如下:(所以本文的代码主要以这种写法来讲解)

@autoreleasepool {
// code
}
  • 既然autoreleasepool也是一个对象,它在内存中以什么结构进行存储呢?它存储于内存中的栈中,遵循”先进后出”原则。

下面通过代码来简单的看一下autoreleasepool是如何进行内存管理的。

- ####新建一个HXPerson类,重写其dealloc方法,代码如下:

- (void)dealloc {
NSLog(@"HXPerson dealloc"); [super dealloc]; }
  • 无autoreleasepool情况:

int main(int argc, const charchar * argv[]) {  

    HXPerson *person = [[HXPerson alloc] init];
[person release];
return 0;
}
  • 有autoreleasepool情况:

int main(int argc, const charchar * argv[]) {
@autoreleasepool {
// 根据上面介绍的,我们要在初始化的时候,调用autorelease方法
HXPerson *person = [[[HXPerson alloc] init] autorelease];
}
return 0;
}
  • 可以看到,使用autoreleasepool的情况就算没有调用release方法,该person对象也被销毁了。但是在创建person对象的时候一定要调用autorelease方法。该方法主要的作用就是将person对象放在该autoreleasepool中,且person对象在该autoreleasepool没有销毁之前一直是有效的,也就是说该person对象可以被访问,直到该autoreleasepool被销毁。只要autoreleasepool被销毁,放在autoreleasepool里面的所有对象(调用过autorelease的对象)都会自动执行一次release方法来销毁对象。

autorelease作用:

1.对象执行autorelease方法时会将对象添加到自动释放池中

2.当自动释放池销毁时自动释放池中所有对象作release操作

3.对象执行autorelease方法后自身引用计数器不会改变,而且会返回对象本身

4.autorelease实际上只是把对象release的调用延迟了,对于对象的autorelease系统只是把当前对象放入了当前对应的autorelease pool中,当该pool被释放时([pool drain]),该pool中的所有对象会被调用Release,从而释放使用的内存。这个可以说是autorelease的优点,因为无需我们再关注他的引用计数,直接交给系统来做!

5.对于操作占用内存比较大的对象的时候不要随便使用,担心对象释放的时间太迟,造成内存高峰, 但是操作占用内存比较小的对象可以使用

  • 在创建对象的时候,调用autorelease,就能将该对象放到autoreleasepool中。利用autoreleasepool的延迟释放来管理内存。autoreleasepool这么重要,可是我们在实际开发中并没有手动创建autoreleasepool,却没有内存泄露。这是为什么呢?其实没有手动创建并不代表它不会被创建,那么它是什么时候创建的呢?

autoreleasepool创建

  • 上篇文章中讲到runLoop的时候就提到autoreleasepool。 App启动后,系统在主线程runLoop里注册两个Observser,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其优先级最高,保证创建释放池发生在其他所有回调之前。第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 优先级最低,保证其释放池子发生在其他所有回调之后。在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被runLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄漏,开发者也不必显示创建Pool。可见开发过程中我们没有创建autoreleasepool,系统也会帮我们创建。这就解释了,为什么开发中没有创建autoreleasepool也没有内存泄露的原因了。RunLoop

  • 通过下面的例子,我们来看一下,runLoop创建的autoreleasepool是不是真的帮我们管理了内存。


__weak id reference = nil;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = [NSString stringWithFormat:@"autoreleasePool"];
// str是一个autorelease对象,设置一个weak的引用来观察它
reference = str;
NSLog(@"%@", reference); // Console: autoreleasePool
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%@", reference); // Console: autoreleasePool
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"%@", reference); // Console: (null)
}
  • 这个实验同时也证明了viewDidLoad和viewWillAppear是在同一个runloop调用的,而viewDidAppear是在之后的某个runloop调用的。由于这个vc在loadView之后便add到了window层级上,所以viewDidLoad和viewWillAppear是在同一个runloop调用的,因此在viewWillAppear中,这个autorelease的变量依然有值。

  • 当然,我们也可以不用等到当前runLoop结束,选择手动干预Autorelease对象的释放时机:

- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"autoreleasePool"];
}
NSLog(@"%@", str); // Console: (null)
}
  • 通过上面的例子,可以看出,没有调用release也做到了内存管理。可是大家注意到了,str对象没有调用autorelease方法啊,怎么被放到autoreleasepool进行管理的呢?其实静态方法已经在内部自动调用了autorelease方法,所有这里不需要再调用。

autoreleasepool作用

autoreleasepool实质

  • 现在以ARC环境来分析其原理。runLoop创建的autoreleasepool实例我们就以@autoreleasepool形式呈现。新建项目之后,其中的main函数如下:

int main(int argc, charchar * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
  • 在这个 @autoreleasepool{}中只包含了一行代码,这行代码将所有的事件、消息全部交给了 UIApplication 来处理,但是这不是本文关注的重点。

  • 需要注意的是:整个iOS的应用都是包含在一个自动释放池block中的。

  • 继续我们的主题。我们知道autoreleasepool是一个自动释放池,那么它到底是一个什么样的数据结构呢?我们在命令行中使用 clang -rewrite-objc main.m 让编译器重新改写这个文件,编译完后,会在该文件目录下多一个.cpp文件。打开这个文件。滚到最底部。可以看到如下代码:(删除掉多余的代码)

int main(int argc, const charchar * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
  • 在这个文件中,有一个非常奇怪的 __AtAutoreleasePool 的结构体,前面的注释写到/* @autoreleasepopl */ 。也就是说@autoreleasepool {} 被转换为:

{
__AtAutoreleasePool __autoreleasepool;
}
  • 那么__AtAutoreleasePool又是什么?在文件中可以找到__AtAutoreleasePool数据结构如下:

struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
voidvoid * atautoreleasepoolobj;
};
  • 它是一个结构体,该结构体会在初始化时调用 objc_autoreleasePoolPush() 方法,会在析构时调用objc_autoreleasePoolPop 方法。所以我们可以进一步将main函数中的代码改写为如下:

int main(int argc, const charchar * argv[]) {
{
voidvoid * atautoreleasepoolobj = objc_autoreleasePoolPush(); // do whatever you want objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
  • @autoreleasepool 只是帮助我们少写了这两行代码而已,让代码看起来更美观,然后要根据上述两个方法来分析自动释放池的实现。

  • objc_autoreleasePoolPush 和 objc_autoreleasePoolPop 的实现:

voidvoid *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
} void objc_autoreleasePoolPop(voidvoid *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
  • __AtAutoreleasePool的Push和Pop方法看上去方法看上去是对 AutoreleasePoolPage 对应静态方法push 和 pop 的封装。

AutoreleasePoolPage

那么AutoreleasePoolPage又是一个什么东东呢?,它的定义可以在NSObject.mm文件中看到,定义如下:

class AutoreleasePoolPage {
magic_t const magic;
idid *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
  • magic 用于对当前 AutoreleasePoolPage 完整性 的校验
  • thread 保存了当前页所在的线程
  • 每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是4096 字节(16 进制 0x1000)

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES
  • parent 和 child 就是用来构造双向链表的指针。

  • 自动释放池中的 AutoreleasePoolPage 是以 双向链表 的形式连接起来的:

转自其他

内存管理总结-autoreleasePool的更多相关文章

  1. 菜鸟学习Cocos2d-x 3.x——内存管理

    菜鸟学习Cocos2d-x 3.x——内存管理 2014-12-10 分类:Cocos2d-x / 游戏开发 阅读(394) 评论(6)    亘古不变的东西 到现在,内存已经非常便宜,但是也不是可以 ...

  2. cocos2d-x内存管理

    Cocos2d-x内存管理 老师让我给班上同学讲讲cocos2d-x的内存管理,时间也不多,于是看了看源码,写了个提纲和大概思想 一.   为什么需要内存管理 1. new和delete 2. 堆上申 ...

  3. iOS开发系列—Objective-C之内存管理

    概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没 ...

  4. ARC内存管理机制详解

    ARC在OC里面个人感觉又是一个高大上的牛词,在前面Objective-C中的内存管理部分提到了ARC内存管理机制,ARC是Automatic Reference Counting---自动引用计数. ...

  5. objective-c 语法快速过(6)内存管理原理

    内存管理基本原理(最重要) 移动设备的内存极其有限(iphone 4内存512M),每个app所能占用的内存是有限制的(几十兆而已). 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不 ...

  6. 06OC之内存管理

    在高级语言中,例如C#是通过垃圾回收机制(GC)来解决这个问题,但是在OC并没有类似的垃圾回收机制,因此必须由程序员手动去维护.今天就讲讲OC中的内存管理: 一.内存管理原理 在Xcode4.2之后的 ...

  7. iOS经典面试题总结--内存管理

    iOS经典面试题总结--内存管理 内存管理 1.什么是ARC? ARC是automatic reference counting自动引用计数,在程序编译时自动加入retain/release.在对象被 ...

  8. 【Cocos2d-x 3.x】内存管理机制与源码分析

    侯捷先生说过这么一句话 :  源码之前,了无秘密. 要了解Cocos2d-x的内存管理机制,就得阅读源码. 接触Cocos2d-x时, Cocos2d-x的最新版本已经到了3.2的时代,在学习Coco ...

  9. iOS开发ARC内存管理技术要点

    本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇 ...

随机推荐

  1. Linux的xshell命令

    1,Linux基本命令行的组成结构 2,Linux系统命令操作格式 命令 空格 参数 空格 需要处理的内容 rm   -rf   /tmp/* ls   -la   /home 一般情况下(参数)是可 ...

  2. MVC框架显示层——Velocity技术

    Velocity,名称字面翻译为:速度.速率.迅速,用在Web开发里,用过的人可能不多,大都基本知道和在使用Struts,到底Velocity和Struts(Taglib和Tiles)是如何联系?在技 ...

  3. 修改系统时间(取得服务器时间,使用SetLocalTime API函数,需要UAC权限)

    我的客户遇到系统时间不对,自己又不会改,于是想到利用服务端时间来修改本地的系统时间. 第一步,把下面xml存成uac.xml文件备用. <?xml version="1.0" ...

  4. YTU 2811: 打鱼还是晒网

    2811: 打鱼还是晒网 时间限制: 1 Sec  内存限制: 128 MB 提交: 192  解决: 150 题目描述 中国有句俗话"三天打鱼,两天晒网".小王从2000年的1月 ...

  5. phpstorm更改sql文件匹配类型

    正常情况下,sql文件都有对应的文件类型.但是默认的sql文件只是关联普通的sql.很多语法都无法高亮,以及自动提醒.

  6. facebook Presto SQL分析引擎——本质上和spark无异,分解stage,task,MR计算

    Presto 是由 Facebook 开源的大数据分布式 SQL 查询引擎,适用于交互式分析查询,可支持众多的数据源,包括 HDFS,RDBMS,KAFKA 等,而且提供了非常友好的接口开发数据源连接 ...

  7. Gym 100548F Color (数论容斥原理+组合数)

    题意:给定 m 种颜色,把 n 盆花排成一直线的花涂色.要求相邻花的颜色不相同,且使用的颜色恰好是k种.问一共有几种涂色方法. 析:首先是先从 m 种颜色中选出 k 种颜色,然后下面用的容斥原理,当时 ...

  8. POJ3682;King Arthur's Birthday Celebration(期望)

    传送门 题意 进行翻硬币实验,若k次向上则结束,进行第n次实验需花费2*n-1的费用,询问期望结束次数及期望结束费用 分析 我们令f[i]为结束概率 \[f[i]=C_{i-1}^{k-1}*p^k* ...

  9. Marching squares 算法 获取轮廓(contour tracing)

    https://en.wikipedia.org/wiki/Marching_squares  http://blog.csdn.net/coolingcoding/article/details/1 ...

  10. 洛谷 P4552 [Poetize6] IncDec Sequence【差分+脑洞】

    一看区间操作,很容易想到差分 所以就是先差分,然后为了保证最小步数,把政府差分抵消,也就相当于原数组区间加减 第二问,因为差分数组抵消之后不为0就需要使用n+1的虚拟位置,而这个的值其实没有,所以我们 ...