Autorelease Pool-自动释放池

    Autorelease Pool是Objective-C中的内存管理方式之一,它与线程和NSAutorelease类有关。每一个线程都拥有自己的Autorelease Pool栈,这个栈底层是由双向链表构成,而双向链表由类AutoreleasePoolPage表示。AutoreleasePoolPage是一个C++类,这个类的结构如下(只给出了部分相关成员变量):

class AutoreleasePoolPage  {
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;  //和线程相关的key值
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id); //每一个page的大小
    id *next; //加入当前page中的Objective-C对象地址会传给next
    pthread_t const thread;  //线程的引用
    AutoreleasePoolPage * const parent;  //指向父节点
    AutoreleasePoolPage *child;  //指向子节点
 };

 线程与Autorelease Pool栈的关系如下图所示:

上图中,加颜色部分表示加入到自动释放池中的对象,而next总是指向下一个可用位置。当前线程总是可以通过AutoreleasePoolPage中的key属性获取到位于栈最顶端(图中就是最右边)的AutoreleasePoolPage对象,这个对象被称为hot page。

    需要注意的是,并不是一个AutoreleasePoolPage对象就代表一个自动释放池,一个自动释放池可以横跨多个AutoreleasePage对象。自动释放池之间是通过哨兵(Sentinel)来分隔的。那么什么是哨兵,NSAutoreleasePool对象和AutoreleasePoolPage对象之间又有什么关系呢?

    当我们使用如下方法创建NSAutoreleasePool对象时:

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

NSAutoreleasePool对象的init方法实际上会调用AutoreleasePoolPage类的static push方法:

  static inline void *push() 
    {
        id *dest = autoreleaseFast(POOL_SENTINEL);
        assert(*dest == POOL_SENTINEL);
        return dest;
    }

AutoreleasePoolPage::push方法中的autoreleaseFast方法传入的就是一个哨兵,其实就是一个nil值。而AutoreleasePoolPage::autoreleaseFast方法会做3件事:

1 通过AutoreleasePoolPage的key属性获取当前线程对应的hot page,如果hot page存在并且未满,那么就向当前page中加入哨兵;

2 如果当前hot page已满,那么就创建一个新的AutoreleasePoolPage对象,加入到链表最顶端使成为hot page,并将哨兵加入到新的hot page中;

3 如果hot page根本不存在,那么也会创建一个新的AutoreleasePoolPage对象,使之成为hot page,并将哨兵加入到新hot page中。

当加入了一个哨兵之后,就代表一个新自动释放池开始。AutoreleasePoolPage::push方法会将这个哨兵地址返回回去,而NSAutoreleasePool对象会持有这个哨兵地址:

@interface NSAutoreleasePool : NSObject {
@private
    void    *_token;   //保存哨兵地址
    void    *_reserved3;
    void    *_reserved2;
    void    *_reserved;
}

NSAutoreleasePool对象当中的_token成员,就是用来保存哨兵地址的。因此,线程,AutoreleaesPoolPage对象,NSAutoreleasePool对象之间的关系可以表示成:

上图中有两个哨兵,因此有两个自动释放池,第一个自动释放池由NSAutoreleasePool对象_1表示,范围从第一个哨兵开始到第二个哨兵下面,第二个自动释放池由NSAutoreleasePool对象_2表示,范围从第二个哨兵开始直到结束。这也是自动释放池嵌套时的情形。

 

    当自动释放池释放,即调用NSAutoreleasePool对象的release或者drain时会发生什么情况呢?

    当调用NSAutoreleasePool对象的release或者drain方法时,它们将NSAutoreleasePool对象保存的哨兵地址传递会给AutoreleasePoolPoage的static pop方法,这个AutoreleasePoolPage::pop方法主要做了如下2件事:

1 通过相应算法,根据哨兵地址找到哨兵所在的AutoreleasePoolPage对象;

2 从hot page开始,直到哨兵对象为止,依次弹出加入到里面的Objective-C对象,并且向Objective-C对象发送release消息

假设释放了上面的NSAutoreleasePool对象_2,那么结果如下图所示:

 

从Apple关于NSAutoreleasePool的官方文档我们可以知道,NSAutoreleasePool对象无法被retain,因此,当AutoreleasePoolPage::pop方法调用完成之后,NSAutoreleasePool对象的release或者drain方法就会释放掉这个NSAutoreleasePool对象本身。到这里为止,我们又可以向上一个自动释放池当中添加新的Objective-C对象了。

posted @ 2015-10-31 17:40  chaoguo1234  阅读(678)  评论(0编辑  收藏  举报