Autoreleasepool分析

main函数是整个程序的入口,从我们一进入main 函数就会接触到autoreleasepool。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

整个iOS应用都是包含在一个自动释放池block中。

使用clang -rewrite-objc对main.m编译之后我们会发现autoreleasepool@{}被转换成为一个__AtAutoreleasePool结构体

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

实际上,我们的main函数在工作时是这样的

int main(int argc, const char * argv[]) {
    {
        void * autoreleasepoolobj = objc_autoreleasePoolPush();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

autoreleasepool的实际是调用objc_autoreleasePoolPush和objc_autoreleasePoolPop方法

AutoreleasePoolPage分析

objc_autoreleasePoolPush和objc_autoreleasePoolPop的实现:

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

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

通过方法内部我们能看到,push和pop方法是对AutoreleasePoolPage两个方法的封装。

AutoreleasePoolPage是C++的一个类,定义如下:

class AutoreleasePoolPage {
    magic_t const magic;        //对当前AutoreleasePoolPage完整性的校验
    id *next;     //指向栈顶最新add进来的autorelease对象的下一个位置 
    pthread_t const thread; //当前类所在的线程
    AutoreleasePoolPage * const parent; //指向上一个AutoreleasePoolPage
    AutoreleasePoolPage *child; //指向下一个AutoreleasePoolPage
    uint32_t const depth;  //深度
    uint32_t hiwat; 
};

每一个自动释放池都是由一系列AutoreleasePoolPage组成的,每一个AutoreleasePoolPage大小都是4096字节。
自动释放池中的AutoreleasePoolPage是以双向链表的形式连接起来的:

一个AutoreleasePoolPage的空间被占满的时候,会新建一个AutoreleasePoolPage对象,连接链表。

如果一个AutoreleasePoolPage初始化在内存的0x100816000 ~ 0x100817000中,那么他的结构就像这样:

0x100816038 ~ 0x100817000用来储存加入到自动释放池中的对象。

POOL_SENTINEL(哨兵对象)

上图中能够看到有一个哨兵对象,他本质就是nil。
#define POOL_SENTINEL nil
每当进行一次objc_autoreleasePoolPush的时候,runtime会向当前的AutoreleasePoolPage中添加一个哨兵对象,值为nil,然后将这个哨兵对象的地址返回,被objc_autoreleasePoolPop(哨兵对象)当作入参。

int main(int argc, const char * argv[]) {
    {
        void * autoreleasepoolobj = objc_autoreleasePoolPush();
        //do some thing
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

autoreleasepoolobj就是哨兵对象。
当方法objc_autoreleasePoolPop调用的时候,就会向自动释放池中的所有晚于autorelease对象发送一次release消息,从最新加入的对象一直向前清理,可以跨越多个page,一直到哨兵所在的page。并向回移动next指针到正确的位置。

objc_autoreleasePoolPush分析

objc_autoreleasePoolPush()

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

AutoreleasePoolPage 的类方法push

static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}

这里会进入到一个方法autoreleaseFast,并且传入哨兵对象。

static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {
       return page->add(obj);
   } else if (page) {
       return autoreleaseFullPage(obj, page);
   } else {
       return autoreleaseNoPage(obj);
   }
}

上面可以看到有一个hotpage,hotpage可以理解为当前正在使用的AutoreleasePoolPage,有三种执行情况:

  • 有hotpage并且hotpage不满的时候,调用page->add(obj)将对象加入AutoreleasePoolPage栈中。
  • 有hotpage并且hotpage已满的时候,调用autoreleaseFullPage,初始化新页,然后调用page->add(obj)将对象加入AutoreleasePoolPage栈中。
  • 无hotpage的时候,调用autoreleaseNoPage创建一个hotPage,然后调用page->add(obj)将对象加入AutoreleasePoolPage栈中。

objc_autoreleasePoolPop分析

objc_autoreleasePoolPop

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

AutoreleasePoolPage的类方法pop(ctxt);

static inline void pop(void *token) {
    AutoreleasePoolPage *page = pageForPointer(token);
    id *stop = (id *)token;

    page->releaseUntil(stop);

    if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        } else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

这个方法主要做了三件事:

  1. 使用pageForPointer获取当前token所在的AutoreleasePoolPage,就是哨兵对象所在的AutoreleasePoolPage
  2. 调用releaseUntil方法释放栈中的对象,一直到stop指针。
  3. 调用childkill方法。

小结

这篇文章是我在查阅了一些资料结合自己的一些实践之后总结的一些对于AutoreleasePool的理解,更多的是一个备忘的作用,让自己加深印象,最后再总结一下。

  • 自动释放池实际上是调用了AutoreleasePoolPage的push和pop方法向里面加入和释放对象。
  • AutoreleasePoolPage是以双向链表实现的。
  • 其中需要注意的是哨兵对象的含义以及push和pop方法的具体实现。

参考:
Using Autorelease Pool Blocks
NSAutoreleasePool - Foundation | Apple Developer Documentation
黑幕背后的Autorelease · sunnyxx的技术博客