再谈 AutoReleasePool

644 阅读4分钟



结构:
简单说是双向链表,每张链表头尾相接。每创建一个池子,会在首部创建一个 哨兵 对象,作为标记。最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表。

1、Autorelease 概述

当向一个对象发送一个autorelease消息时,Cocoa就会将该对象的一个引用放入到最新的自动释放池。
它仍然是个正当的对象,因此 自动释放池定义的作用域内 的其它对象可以向它发送消息
当自动释放池释放时,其中所有被管理对象都会收到”release”的消息, 从而池中的所有对象也就被释放
注意,同一个对象可以被多次调用”autorelease”方法,并可以放到同一个”AutoreleasePool”中。 所以引入这个自动释放池机制,对象的”autorelease”方法代替”relrease”方法可以延长它的生命周期,直接到当前”AutorelreasePool”释放。

很多人说, 当程序执行到作用域结束的位置时(当前作用域大括号结束时),自动释放池就会被释放,这个说法是不对的。正确的过程是如何呢?

iOS的运行时是由一个一个runloop组成的,每个runloop都会执行下图的一些步骤:

这里写图片描述
可以看到,每个runloop中都创建一个Autorelease Pool,并在runloop的末尾进行释放,所以,一般情况下,每个接受autorelease消息的对象,都会在下个runloop开始前被释放。

也就是说,在一段同步的代码中执行过程中,生成的对象接受autorelease消息后,一般是不会在作用域结束前释放的。
所以严谨的说, 在没有手动添加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。 



2、AutoreleasePool 与 RunLoop

  • App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。

  • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

  • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

以 alloc, copy, init,mutableCopy和new这些方法打头的方法,返回的都是
retained return value,例如[[NSString alloc] initWithFormat:],

其他的则是unretained return value

对于前者调用者是要负责释放的,后者不需要负责释放

后者 ARC会把对象的生命周期延长,确保调用者能拿到并且使用这个返回值,但是并不一定会使用 autorelease,在worst case 的情况下才可能会使用,因此调用者不能假设返回值真的就在 autorelease pool中。从性能的角度,这种做法也是可以理解的。如果我们能够知道一个对象的生命周期最长应该有多长,也就没有必要使用 autorelease 了,直接使用 release 就可以。如果很多对象都使用 autorelease 的话,也会导致整个 pool 在 drain 的时候性能下降。

总结:
1.方法里的临时变量是会通过autoreleasepool释放的

2.NSCFString跟普通对象一样是可以释放的

3.NSString和NSArray的工厂方法可以延长对象的生命周期(同理,NSDictionary也是一样的,有兴趣的可以试一下)


在ARC下,已经不允许使用NSAutoreleasePool对象了,并且根据官方文档,@autoreleasepool比它更高效,因此这里只讨论@autoreleasepool。

最重要的两个入口函数如下:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

push操作往poolpage中插入一个标记,
而pop则给这一区间的所有对象发送release消息。

有一种说法是:

在子线程, 在每次运行循环结束的时候执行释放操作

这一种说法的原因是,
在主线程,当前runloop,可以明显看到注册了几种observer
activities = 0x1,对应的就是kCFRunLoopEntry
activities = 0xa0,对应的就是kCFRunLoopBeforeWaiting | kCFRunLoopExit
它们的回调函数为_wrapRunLoopWithAutoreleasePoolHandler (),这个函数在entry的时候调用push(),在beforeWaitting的时候调用pop()push(),在exit时调用pop

总结

@autoreleasepool{}复制代码

等价于

void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
每次出了{}时objc_autoreleasePoolPop()就被调用, 所以直接释放掉了.




OC的内存管理
每当碰到release和autorelease的时候引用计数就会减一,如果此对象的计数变为了0, 就会被系统销毁.