AutoreleasePoolPage自动释放池原理


所有app的入口都是一个main函数

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

可以发现:整个 iOS 的应用都是包含在一个自动释放池 block 中的
@autoreleasepool{} 本质上是一个结构体:

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

这个结构体在初始化的时候会调用:objc_autoreleasePoolPush() 方法
在析构的时候,会调用:objc_autoreleasePoolPop 方法
这表明,main函数实际工作的时候,是这样的:

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

 objc_autoreleasePoolPushobjc_autoreleasePoolPop是什么呢?

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

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

上面的方法看上去是对 AutoreleasePoolPage 对应静态方法 push 和 pop 的封装。
AutoreleasePoolPage结构如下:

class AutoreleasePoolPage {
    magic_t const magic;//AutoreleasePoolPage 完整性校验
    id *next;//下一个存放autorelease对象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的线程
    AutoreleasePoolPage * const parent;//父节点
    AutoreleasePoolPage *child;//子节点
    uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
    uint32_t hiwat;
}

每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES

自动释放池就是由 AutoreleasePoolPage 构成的双向链表

单个 AutoreleasePoolPage 结构如下:

 其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的 0x100816038 ~ 0x100817000 都是用来存储加入到自动释放池中的对象。

1:begin() 和 end() 这两个类的实例方法帮助我们快速获取 0x100816038 ~ 0x100817000 这一范围的边界地址。
2:next 指向下一个为空的内存地址,如果 next 指向的地址加入一个 object,它就会如下图所示移动到下一个为空的内存地址中。
 

POOL_SENTINEL(哨兵对象)

POOL_SENTINEL 只是 nil 的别名。

#define POOL_SENTINEL nil

在每个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。 

int main(int argc, const char * argv[]) {
    {
    	//这里的 atautoreleasepoolobj 就是一个 POOL_SENTINEL
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();
        
        // do whatever you want
        
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

上面的 atautoreleasepoolobj 就是一个 POOL_SENTINEL。
而当方法 objc_autoreleasePoolPop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL:

objc_autoreleasePoolPush 方法 

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

这里调用了 AutoreleasePoolPage::push() 方法,hotPage指的是当前正在使用的 AutoreleasePoolPage

static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {//有 hotPage 并且当前 page 不满,将object加入当前栈中
       return page->add(obj);
   } else if (page) {//有hotPage 但当前page已满,找为满页或创建新页,将object添加到新页中
       return autoreleaseFullPage(obj, page);
   } else {//无hotPage,创建hotPage,加入其中
       return autoreleaseNoPage(obj);
   }
}
  1. 有 hotPage 并且当前 page 不满,直接调用 page->add(obj) 将对象添加到自动释放池中。
    /就是一个入栈操作
    id *add(id obj) {
        id *ret = next;
        *next = obj;
        next++;
        return ret;
    }
    

  2. 有hotPage 但当前page已满,找为满页或创建新页,将object添加到新页中autoreleaseFullPage (当前page满的时候调用)
    static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    //一直遍历,直到找到一个未满的 AutoreleasePoolPage,如果找到最后还没找到,就新建一个 AutoreleasePoolPage
        do {
            if (page->child) 
            	page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
    	
    	//将找到的,或者构建的page作为hotPage,然后将obj加入
        setHotPage(page);
        return page->add(obj);
    }
    

无hotPage,创建hotPage,加入其中,这个时候,由于内存中没AutoreleasePoolPage,就要从头开始构建这个自动释放池的双向链表,那么当前页表作为第一张页表,是没有parent指针的。

static id *autoreleaseNoPage(id obj) {
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);

    if (obj != POOL_SENTINEL) {
        page->add(POOL_SENTINEL);
    }

    return page->add(obj);
}

objc_autoreleasePoolPop

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

我们一般都会在这个方法中传入一个哨兵对象 POOL_SENTINEL,如下图一样释放对象:

总的来说,autoreleasepool就是一个双向链表,链表中的每个节点是一个栈,栈中保存了指向 autoreleasepool的指针,所以在 autoreleasepool 中的所有对象引用计数都会+1,一旦出了 autoreleasepool,没有指针指向对象,对象的引用计数就会-1,ARC下,xcode会为代码自动添加 autoreleasepool。
 

猜你喜欢

转载自blog.csdn.net/wywinstonwy/article/details/123269931