全面深入理解NSAutoreleasePool

本文详细解释了NSAutoreleasePool的工作原理及其在Objective-C内存管理中的角色。包括自动释放池的创建与销毁时机、对象自动释放的过程以及如何合理使用自动释放池避免内存问题。

先要弄清楚的疑问

既然设置了ARC,为什么还要使用@autoreleasepool?

ARC 并不是舍弃了 @autoreleasepool,而是在编译阶段帮你插入必要的 retain/release/autorelease 的代码调用。

所以,跟你想象的不一样,ARC 之下的autorelease,依然是依赖于 NSAutoreleasePool,跟非 ARC 模式下手动调用那些函数本质上毫无差别,只是编译器来做会保证引用计数的正确性。

下面步入正文。

NSAutoReleasePool原理

Cocoa的内存管理主要依赖于Reference Counting, 而NSAutoReleasePool就是用来支持它的。

众所周知,对一个object发送release消息并使其retain count减到0时,对象马上就被销毁了。但有时我们希望对象不被手动释放,而希望它在某个合适的时候被释放,比如我们想在某个方法返回之后再销毁其内部的某个object,那我们需要通过autorelease方法来销毁它。

这个autorelease的机制,在OC中就是用NSAutoreleasePool类来实现的。通过这个pool,声明为autorelease的对象生命就被延长了(当pool被销毁时才对象真正被销毁)。

在每个线程中都会维持一个stack,NSAutoreleasePool 实例是保存在每个线程的stack中。当一个新pool创建,它就会进栈。当一个pool被销毁的时候,它就出栈。 被标记为autorelease的对象会跟最近的pool匹配,即栈顶的pool。

NSAutoreleasePool中包含了一个可变数组,用来存储将要被autorelease的对象。当对象被添加到pool中,其实他是添加到pool中的一个可变数组中去。当pool被销毁的时候,它会遍历这个数组,对数组中的每一个成员发送release消息。

注意:

  1. 这里只是release,并没有直接销毁对象。若成员的retain count 大于1,那么对象没有被销毁,造成内存泄露。
  2. 如果一个pool被销毁,并且这个pool不是在栈顶,这个pool还会销毁该pool之上的所有pool。

要实现对象的autorelease,需要将对象添加到一个autorelease pool中。具体有三种方法:

1.
[NSAutoreleasePool addObject:autoReleasedObj]; 

2.
@autoreleasepool {
        //your code here will be autoreleased
}

3.
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];

ClassA *a=[[[ClassA alloc] init] autorelease];
ClassB *b=[[[ClassB alloc] init] autorelease];
...
[pool release];

NSAutoreleasePool需要在什么时候创建?

自动创建的情况:

  1. iOS application主线程的autorelease pool。就是在新建一个iOS项目的时候,xcode会在main函数里面自动地为你创建一个autorelease pool。

  2. 使用NSThread的detachNewThreadSelector:toTarget:withObject:方法创建新线程时,新线程自动带有autoreleasepool。

以下情况需要开发者创建:

  1. 当你在主线程外开启其它不自动带有autorelease pool的线程时:系统只会在主线程中自动生成并销毁autorelease pool,其他一些线程的autorelease pool需要手动创建。可以嵌套使用NSAutoreleasePool。

  2. 当你在短时间内制造了大量autorelease的对象时:及时地销毁有助于节约iOS设备有限的内存资源。

  3. 创建新的thread, 并在其中访问了Cocoa, 需要在访问之前创建autorelease pool, 访问结束后drain。

NSAutoreleasePool何时被销毁?

答曰:是当一个runloop结束的时候,而不是对象作用域结束的时候!

对于每一个runloop( 一个UI事件,Timer call, delegate call, 都会是一个新的runloop,详见下一篇文章), 系统会隐式创建一个autorelease pool,这样所有的pool会构成一个栈式结构,在每一个runloop结束时,当前栈顶的autorelease pool会被销毁。

对于iOS事件(触控事件、传感器事件、远程控制事件),在每一个事件周期(event cycle)的开始,系统会自动创建一个自动释放池;在每一个事件周期的结尾,系统会自动销毁这个自动释放池。一般情况下,你可以理解为:当你的代码在持续运行时,自动释放池是不会被销毁的,这段时间内你也可以安全地使用自动释放的对象;当你的代码运行告一段落,开始等待用户输入(或者其它事件)时,自动释放池就会被释放。

扩展:如果你极具好奇心,把main函数中的NSAutoreleasePool代码删除掉,然后在自己的代码中把对象声明为autorelease,你会发现系统并不会给你发出错误信息或者警告。用内存检测工具去检测内存的话,你可能会惊奇的发现你的对象仍然被销毁了。这就是因为,在新生成一个runloop的时候,系统会自动的创建一个NSAutoreleasePool,该NSAutoreleasePool无法被删除。

缺点

任何事物都有两面性。即使NSAutoreleasePool看起来没有手动release那么繁琐,但是使用NSAutoreleasePool来管理内存的方法还是不推荐的。因为在一个NSAutoreleasePool里面,如果有大量对象被标记为autorelease,在程序运行的时候,内存会剧增,直到NSAutoreleasePool被销毁的时候才会释放。如果其中的对象足够的多,在运行过程中你可能会收到系统的低内存警告,或者直接crash。

eg:

@autoreleasepool{
    for(int i = 0; i < 1000; i++)
    {
        //创建大量对象        
    }
}

应改为:

for (int i = 0; i <= 1000; i++) {
    NSAutoreleasePool *pool = [NSAutoreleasePool new];//也可以使用@autoreleasePool{}的方式
    //do something
    [pool drain];
}

NSAutoReleasePool深层剖析

一个对象被标记为autorelease后经历了怎么样的过程?

当我们使用@autoreleasepool{}时,编译器实际上将其转化为以下代码:

void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);//当前runloop迭代结束时进行pop操作

而objc_autoreleasePoolPush与objc_autoreleasePoolPop又是什么呢?他们只是对autoreleasePoolPage的一层简单封装,autoreleasePoolPage的结构如下图,它是C++数据类型,本质是一个双向链表。next就是指向当前栈顶的下一个位置。

这里写图片描述

里面还有各种参数,不过记住这句话就行:向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置。

在文章的最后顺便提一下,在iOS中有三种常用的遍历方法:for、forin、enumerateObjectsUsingBlcok。实际使用中大家可能没有感觉到又什么区别,前面两个比较常用,最后一个是iOS特有的遍历方式,但事实上还是有区别的。block版本的遍历方式已经内嵌了@autoreleasepool{}操作,而前面两个没有,这样就意味着使用block版本的遍历方式会使app更加健壮,内存使用效率更加出色!

Tips

  1. 没有方法知道一个对象是否已经被自动释放了。

  2. 如果对象被autorelease 两次,这个对象就会被两次添加到pool中,当pool被销毁,该对象就会被release两次。

  3. 自动释放池的对象是当当前自动释放池被释放时被释放。pool是当它的代码显性的销毁它时而被销毁。
    如果在一个线程上autorelease一个对象,并将它传给另一个线程,不会有特殊处理。当第一个线程池被销毁的时候,该对象也会被release, 不管该对象在新线程发生了什么。如果你需要继续保留这个对象,就需要在传递之前retain这个对象。

  4. pool不能被retain, 因为在drain的时候默认就销毁它自身了。

  5. 通常在销毁pool的时候用的不是它的release方法, 而是drain! 这是为了让程序同时兼容Reference Counting内存管理环境与Garbge Collection环境。因为在Garbage Colloection环境下,release不做任何操作,而由drain触发垃圾回收的动作。



参考:
http://eleda.iteye.com/blog/1108700
http://blog.sina.com.cn/s/blog_796ffec50100zoix.html
http://my.oschina.net/u/566401/blog/182956
http://www.cnblogs.com/whyandinside/p/3489951.html
http://blog.youkuaiyun.com/horse20000/article/details/7535675
http://segmentfault.com/q/1010000002168217
http://www.cnblogs.com/wengzilin/p/4351187.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值