iOS之autoreleasepool详解

文章目录

        • POOL_SENTINEL(哨兵对象)
        • objc_autoreleasePoolPush 方法
        • objc_autoreleasePoolPop

所有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;
    pthread_t const thread;	//所在线程
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

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

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES

自动释放池就是由 AutoreleasePoolPage 构成的双向链表
iOS之autoreleasepool详解_第1张图片
单个 AutoreleasePoolPage 结构如下:
iOS之autoreleasepool详解_第2张图片
其中有 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:
iOS之autoreleasepool详解_第3张图片

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;
}
  1. 有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);
}
  1. 无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,如下图一样释放对象:
iOS之autoreleasepool详解_第4张图片
总的来说,autoreleasepool就是一个双向链表,链表中的每个节点是一个栈,栈中保存了指向 autoreleasepool的指针,所以在 autoreleasepool 中的所有对象引用计数都会+1,一旦出了 autoreleasepool,没有指针指向对象,对象的引用计数就会-1,ARC下,xcode会为代码自动添加 autoreleasepool。

但有个问题一直不是太明白,网上的博客都说每轮 runloop调用时,会调用 autoreleasepool 的push,结束时会调用pop,这就是 autoreleasepool为什么能释放对象的原因。但是对于我们自己添加的 autoreleasepool,系统会为这个 autoreleasepool 创建一个runloop么?如果不是,为什么自己添加的autoreleasepool在结束后,就会对其中的对象进行释放呢?

参考文献:
1:Objective-C Autorelease Pool 的实现原理 http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/
2:自动释放池的前世今生 https://github.com/Draveness/analyze/blob/master/contents/objc/自动释放池的前世今生.md

你可能感兴趣的:(iOS)