原文连接git
AutoreleasePool对于iOS开发者来讲,能够说是"熟悉的陌生人"。熟悉是由于每一个iOS程序都被包围在一个autoreleasepool中,陌生是由于整个autoreleasepool是黑盒的,开发者看不到autoreleasepool中发生了什么,并且项目开发中直接用到autoreleasepool的地方很少。本文结合Runtime源码,分析一下AutoreleasePool的内部实现。github
咱们都知道,iOS程序的入口是main.m文件中的main方法。在Xcode中新建一个iOS项目,Xcode会自动生成main.m文件。main.m文件中只有一个main方法,绝大多数状况下,不须要修改main.m中的代码。面试
一个典型的main函数:bash
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
能够看到,main函数的函数体是包含在一个autoreleasepool中的。惋惜的是,经过command + 鼠标左键,并不能看到autoreleasepool的定义。不过咱们可使用clang,将main.m文件编译成C++代码,看看autoreleasepool发生了什么。数据结构
使用命令:函数
clang -rewrite-objc main.m
复制代码
生成main.cpp文件。oop
生成的main.cpp文件很大,大概有10w行,不须要关注文件到底有多少行。将文件拖到最下面,看一下main函数变成了什么:ui
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_09_mbt6ttpn7_39cpx9j6zg6h440000gp_T_main_f1e080_mi_0);
return 0;
}
}
复制代码
整个函数的函数体被包围在了__AtAutoreleasePoool __autoreleasepool中。并且前面有关于@autoreleasepool的注释,所以能够猜想autoreleasepool被表示成了__AtAutoreleasePool。this
在main.cpp中搜索一下,看看__AtAutoreleasePool是什么。spa
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
复制代码
__AtAutoreleasePool是一个结构体,结构体中包含构造函数和析构函数。构造函数中调用了
atautoreleasepoolobj = objc_autoreleasePoolPush();
复制代码
析构函数中调用了
objc_autoreleasePoolPop(atautoreleasepoolobj);
复制代码
因而,关注的重点就成了objc_autoreleasePoolPush和objc_autoreleasePoolPop函数。
objc_autoreleasePoolPush和objc_autoreleasePoolPop函数在Runtime源码中能够找到,位于NSObject.mm文件中。
看一下Runtime源码中objc_autoreleasePoolPush和objc_autoreleasePoolPop函数的实现。
void * objc_autoreleasePoolPush(void)
{
// 调用了AutoreleasePoolPage中的push方法
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
// 调用了AutoreleasePoolPage中的pop方法
AutoreleasePoolPage::pop(ctxt);
}
复制代码
经过源码能够看到分别调用了AutoreleasePoolPage的push方法和pop方法。
AutoreleasePoolPage的定义位于NSObject.mm文件中:
// AutoreleasePoolPage的大小是4096字节
class AutoreleasePoolPage
{
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
// 哨兵对象
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
// AutoreleasePoolPage的大小,经过宏定义,能够看到是4096字节
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);
magic_t const magic;
// 一个AutoreleasePoolPage中会存储多个对象
// next指向的是下一个AutoreleasePoolPage中下一个为空的内存地址(新来的对象会存储到next处)
id *next;
// 保存了当前页所在的线程(一个AutoreleasePoolPage属于一个线程,一个线程中能够有多个AutoreleasePoolPage)
pthread_t const thread;
// AutoreleasePoolPage是以双向链表的形式链接
// 前一个节点
AutoreleasePoolPage * const parent;
// 后一个节点
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
复制代码
除此以外,还定义了不少方法,方法的做用及实现下面会分析。
在上面的定义中,我已经加了些注释。经过注释能够获得:
对AutoreleasePoolPage的定义有了基本的了解以后,来看一下push方法和pop方法。
AutoreleasePoolPage中的push方法,通过简化以后以下:
static inline void *push()
{
id *dest;
// POOL_BOUNDARY其实就是nil
dest = autoreleaseFast(POOL_BOUNDARY);
return dest;
}
复制代码
push方法中主要调用了autoreleaseFast方法,所传入的参数是POOL_BOUNDARY,也就是nil。看你一下autoreleaseFast方法的实现。
static inline id *autoreleaseFast(id obj)
{
// hotPage就是当前正在使用的AutoreleasePoolPage
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
// 有hotPage且hotPage不满,将对象添加到hotPage中
return page->add(obj);
} else if (page) {
// 有hotPage可是hotPage已满
// 使用autoreleaseFullPage初始化一个新页,并将对象添加到新的AutoreleasePoolPage中
return autoreleaseFullPage(obj, page);
} else {
// 无hotPage
// 使用autoreleaseNoPage建立一个hotPage,并将对象添加到新建立的page中
return autoreleaseNoPage(obj);
}
}
复制代码
我在代码中已经加入了注释,再来看一下里面涉及到的一些方法。
// 获取正在使用的AutoreleasePoolPage
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
复制代码
hotPage能够理解成当前正在使用的page。上面也提到了,AutoreleasePoolPage中有parent和child指针,实际上AutoreleasePool就是由一个个AutoreleasePoolPage组成的双向链表。这里获得的hotPage能够理解成链表最末尾的结点。
获取hotPage的方法是tls_get_direct(key),key是AutoreleasePoolPage结构中定义的
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
复制代码
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
复制代码
将某个page设置成hotPage。
// 是否已满
bool full() {
return next == end();
}
复制代码
判断当前的AutoreleasePoolPage是否已满。判断标准是next等于AutoreleasePoolPage的尾地址。上面已经提到了,AutoreleasePoolPage的大小是4096字节,既然大小是固定的,那么确定有满的一刻,full方法就是用来作这个得。
// 将对象添加到AutoreleasePoolPage中
id *add(id obj)
{
id *ret = next; // faster than `return next-1` because of aliasing
// next = obj; next++;
// 也就是将obj存放在next处,并将next指向下一个位置
*next++ = obj;
return ret;
}
复制代码
add方法所作的操做也比较简单,就是将当前对象存放在next指向的位置,而且将next指向下一个位置。能够理解成一个栈,next指针相似于栈的top指针。
// 新建一个AutoreleasePoolPage,并将obj添加到新的AutoreleasePoolPage中
// 参数page是新AutoreleasePoolPage的父节点
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
do {
// 若是page->child存在,那么使用page->child
if (page->child) page = page->child;
// 不然的话,初始化一个新的AutoreleasePoolPage
else page = new AutoreleasePoolPage(page);
} while (page->full());
// 将找到的合适的page设置成hotPage
setHotPage(page);
// 将对象添加到hotPage中
return page->add(obj);
}
复制代码
autoreleaseFullPage所作的操做有三步:
// AutoreleasePool中尚未AutoreleasePoolPage
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// 初始化一个AutoreleasePoolPage
// 当前内存中不存在AutoreleasePoolPage,则从头开始构建AutoreleasePoolPage,也就是其parent为nil
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
// 将初始化的AutoreleasePoolPage设置成hotPage
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool. // 添加一个边界对象(nil) if (pushExtraBoundary) { page->add(POOL_BOUNDARY); } // Push the requested object or pool. // 将对象添加到AutoreleasePoolPage中 return page->add(obj); } 复制代码
autoreleaseNoPage方法处理的是当前autoreleasePool中尚未autoreleasePoolPage的状况。既然没有,须要新建一个AutoreleasePoolPage,且该page的父指针指向空,而后将该page设置成hotPage。以后向该page中先是添加了POOL_BOUNDARY,而后在把对象obj添加到page中。
关于为何须要添加POOL_BOUNDARY的缘由,后面会说到。
如今已经把autoreleaseFast方法中涉及到的方法都弄明白了,再来看一下autoreleaseFast方法作的操做。
static inline id *autoreleaseFast(id obj)
{
// hotPage就是当前正在使用的AutoreleasePoolPage
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
// 有hotPage且hotPage不满,将对象添加到hotPage中
return page->add(obj);
} else if (page) {
// 有hotPage可是hotPage已满
// 使用autoreleaseFullPage初始化一个新页,并将对象添加到新的AutoreleasePoolPage中
return autoreleaseFullPage(obj, page);
} else {
// 无hotPage
// 使用autoreleaseNoPage建立一个hotPage,并将对象添加到新建立的page中
return autoreleaseNoPage(obj);
}
}
复制代码
autoreleaseFast方法首先找到hotPage,也就是当前的page,以后分为三种状况:
至此,AutoreleasePoolPage的push方法介绍完毕。
AutoreleasePoolPage::pop方法的代码通过简化以后以下:
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token);
stop = (id *)token;
page->releaseUntil(stop);
}
复制代码
同理,仍是先看一下里面调用到的方法的实现。不过,在介绍pop内部调用的方法以前,先来看一下pop方法中的参数究竟是什么。
在文章开始处,咱们是从clang重写以后的main.cpp文件引入到Runtime源码的,如今再回过去看一下main.cpp文件中的__AtAutoreleasePool结构体:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
复制代码
objc_autoreleasePoolPop中的参数是atautoreleasepoolobj,而atautoreleasepoolobj是objc_autoreleasePoolPush方法返回的。也就是说,AutoreleasePoolPage中pop方法的参数是AutoreleasePoolPage中push方法返回的,比较拗口,能够多理解一下。那么,AutoreleasePoolPage中push方法返回的是什么呢?
上面已经介绍过push方法了,push方法内部分为了三种状况,不管哪一种状况,最终都调用了add方法,而且返回了add方法的返回值。add方法的实现以下:
id *add(id obj)
{
id *ret = next; // faster than `return next-1` because of aliasing
// next = obj; next++;
// 也就是将obj存放在next处,并将next指向下一个位置
*next++ = obj;
return ret;
}
复制代码
add方法返回的就是所要添加对象在AutoreleasePoolPage中的地址。
而在push方法中,添加的对象是哨兵对象POOL_BOUNDARY,因此,在pop方法中,参数也是哨兵对象POOL_BOUNDARY。
pageForPointer的代码以下:
static AutoreleasePoolPage *pageForPointer(const void *p)
{
// 调用了pageForPointer方法
return pageForPointer((uintptr_t)p);
}
// 根据内存地址,获取指针所在的AutoreleasePage的首地址
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
// 偏移量
uintptr_t offset = p % SIZE;
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
复制代码
pageForPointer方法的做用是根据指针位置,找到该指针位于哪一个AutoreleasePoolPage,并返回找到的AutoreleasePoolPage(以前已经提到了,AutoreleasePool是由一个个AutoreleasePoolPage组成的双向链表,不止一个AutoreleasePoolPage)。
releaseUntil方法的代码以下:
// 释放对象
// 这里须要注意的是,由于AutoreleasePool实际上就是由AutoreleasePoolPage组成的双向链表
// 所以,*stop可能不是在最新的AutoreleasePoolPage中,也就是下面的hotPage,这时须要从hotPage
// 开始,一直释放,直到stop,中间所通过的全部AutoreleasePoolPage,里面的对象都要释放
void releaseUntil(id *stop)
{
// 释放AutoreleasePoolPage中的对象,直到next指向stop
while (this->next != stop) {
// hotPage能够理解为当前正在使用的page
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it // 若是page为空的话,将page指向上一个page // 注释写到猜想这里可使用if,我感受也可使用if // 由于根据AutoreleasePoolPage的结构,理论上不可能存在连续两个page都为空 while (page->empty()) { page = page->parent; setHotPage(page); } // obj = page->next; page->next--; id obj = *--page->next; memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // POOL_BOUNDARY为nil,是哨兵对象 if (obj != POOL_BOUNDARY) { // 释放obj对象 objc_release(obj); } } // 从新设置hotPage setHotPage(this); } 复制代码
代码中我已经加了注释,releaseUntil作的操做就是持续释放AutoreleasePoolPage中的对象,直到next = stop。
回过头来再来看一下pop方法:
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token);
stop = (id *)token;
page->releaseUntil(stop);
}
复制代码
pop方法中主要作了两步:
至此,关于AutoreleasePoolPage以及其中的关键方法就所有介绍完毕了。若是到这里,关于AutoreleasePool、AutoreleasePoolPage、哨兵对象还有点蒙的话,不要着急,继续往下看。
实际上,Runtime中并无AutoreleasePool结构的定义,AutoreleasePool是由AutoreleasePoolPage组成的双向链表,以下图:
在autoreleasepool的开始处,会调用AutoreleasePoolPage的push方法;在autoreleasepool的结束处,会调用AutoreleasePoolPage的pop方法。在AutoreleasePoolPage的push方法中,会往AutoreleasePoolPage中插入哨兵对象,以后的对象依次插入到AutoreleasePoolPage中。以下表示:
AutoreleasePoolPage::push(); // 这里会向AutoreleasePoolPage中插入哨兵对象
*** // 开发者本身写的代码,代码中的对象会依次插入到AutoreleasePoolPage中
***
AutoreleasePoolPage::pop(nil);
复制代码
当AutoreleasePoolPage满以后,会新建一个AutoreleasePoolPage,继续将对象添加到新的AutoreleasePoolPage中。
经过上面的介绍,能够知道,AutoreleasePool由多个AutoreleasePoolPage组成,且AutoreleasePool的开始处一定是一个哨兵对象。到这里,哨兵对象的做用也就清楚了,哨兵对象是用来分隔不一样的AutoreleasePool的。
当调用AutoreleasePoolPage::pop(nil)方法时,会从某个autoreleasepool开始,一直释放到参数哨兵对象所属的autoreleasepool。能够是同一个autoreleasepool,也能够不是同一个autoreleasepool,当不是同一个autoreleasepool时,能够理解成是嵌套autoreleasepool释放。
到这里,AutoreleasePool、AutoreleasePoolPage、哨兵对象之间的关系应该就理解了。
AutoreleasePool在面试中出现的频率也很是高,接下来分享几道关于AutoreleasePool的面试题。
确切地说,应该是AutoreleasePoolPage和线程的关系。AutoreleasePool是由AutoreleasePoolPage组成的双向链表,根据AutoreleasePoolPage的定义,每个AutoreleasePoolPage都属于一个特定的线程。也就是说,一个线程能够有多个AutoreleasePoolPage,可是一个AutoreleasePoolPage只能属于一个线程。
Runloop,即运行循环。从直观上看,RunLoop和AutoreleasePool彷佛没什么关系,其实否则。在一个完整的RunLoop中,RunLoop开始的时候,会建立一个AutoreleasePool,在RunLoop运行期间,autorelease对象会加入到自动释放池中。在RunLoop结束以前,AutoreleasePool会被销毁,也就是调用AutoreleasePoolPage::pop方法,在该方法中,自动释放池中的全部对象会收到release消息。正常状况下,AutoreleasePool中的对象发送完release消息后,引用计数应该为0,会被释放,若是引用计数不为0,则发生了内存泄露。
其实上面已经说过了,AutoreleasePool销毁时,AutoreleasePool中的全部对象都会发送release消息,对象会释放。那么,AutoreleasePool何时销毁呢?分两种状况: