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_autoreleasePoolPush 和 objc_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 构成的双向链表
单个 AutoreleasePoolPage 结构如下:
其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的 0x100816038 ~ 0x100817000 都是用来存储加入到自动释放池中的对象。
1:begin() 和 end() 这两个类的实例方法帮助我们快速获取 0x100816038 ~ 0x100817000 这一范围的边界地址。
2:next 指向下一个为空的内存地址,如果 next 指向的地址加入一个 object,它就会如下图所示移动到下一个为空的内存地址中。
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:
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);
}
}
直接调用 page->add(obj) 将对象添加到自动释放池中。
//就是一个入栈操作
id *add(id obj) {
id *ret = next;
*next = obj;
next++;
return ret;
}
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);
}
static id *autoreleaseNoPage(id obj) {
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
}
return page->add(obj);
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
我们一般都会在这个方法中传入一个哨兵对象 POOL_SENTINEL,如下图一样释放对象:
总的来说,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