autoreleasePool总结
先举个例子,下面这段代码是在非ARC环境下运行:
@autoreleasepool {
AutoRelaseObj *obj = [[AutoRelaseObj new] autorelease];
NSLog(@"\n in pool -----%@",obj);
}
NSLog(@"\n out pool");
通过clang编译后的代码如下:
{
__AtAutoreleasePool __autoreleasepool;
AutoRelaseObj *obj = ((AutoRelaseObj *(*)(id, SEL))(void *)objc_msgSend)((id)((AutoRelaseObj *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AutoRelaseObj"), sel_registerName("new")), sel_registerName("autorelease"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_0aa177_mi_0,obj);
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_0aa177_mi_1);
下面是__AtAutoreleasePool的声明:
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
结合上面的代码,上面的示例的实现大致如下:
atautoreleasepoolobj = objc_autoreleasePoolPush();
AutoRelaseObj *obj = [[AutoRelaseObj new] autorelease];
NSLog(@"\n in pool -----%@",obj);
objc_autoreleasePoolPop(atautoreleasepoolobj);
NSLog(@"\n out pool");
解析来我们分别看下objc_autoreleasePoolPush和objc_autoreleasePoolPop的具体实现;
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
objc_autoreleasePoolPush和objc_autoreleasePoolPop又分别调用AutoreleasePoolPage的push和pop函数;我们先看下AutoreleasePoolPage这个类的结构:
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
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;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
// 成员变量后的第一个入栈位
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
// 最后入栈位
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
// next指向起始位时表示page已经没有autorelease对象了
bool empty() {
return next == begin();
}
// next指向末位时表示page已经没有存满没有多余空间了
bool full() {
return next == end();
}
// 存储一半空间了
bool lessThanHalfFull() {
return (next - begin() < (end() - begin()) / 2);
}
}
AutoreleasePoolPage是AutoreleasePool的核心类,AutoreleasePool的实现就是由多个AutoreleasePoolPage以双向链表的形式连接在一起。下面解释下各成员变量:
- SIZE:每个page的最低容量为PAGE_MAX_SIZE,为4096 /* bytes per 80386 page */
- COUNT:当前page包含多少个autorelease对象
- next:指向最后加入autoreleasepool的对象
- thread:autoreleasepool所在线程,AutoreleasePool是按线程一一对应的
- parent:父结点,指向前一个AutoreleasePoolPage对象
- child:子结点,指向后一个AutoreleasePoolPage对象
- depth:链表的深度,节点个数
- hiwat:数据容纳上限
接着我们看下AutoreleasePoolPage的push函数实现:
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
//POOL_BOUNDARY定义如下
# define POOL_BOUNDARY nil
先来看下autoreleaseFast函数,这是autoreleasePool的关键步骤:
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);
}
}
autoreleaseFast函数先获取hotPage,若当前hotPage存在且没满的情况下直接添加obj。若hotPage存在但没有存储空间了,调用autoreleaseFullPage函数,创建新的page并将对象添加该page中。若page不存在,调用autoreleaseNoPage函数,新建page将对象添加到该page中。
- 获取hotPage
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,这里使用了tls的方法来存取当前的page。TLS是线程局部存储(Thread Local Storage)的缩写,在oc中通过这两个方法来使用,键值对存取:
static inline void *tls_get_direct(tls_key_t k)
{
assert(is_valid_direct_key(k));
if (_pthread_has_direct_tsd()) {
return _pthread_getspecific_direct(k);
} else {
return pthread_getspecific(k);
}
}
static inline void tls_set_direct(tls_key_t k, void *value)
{
assert(is_valid_direct_key(k));
if (_pthread_has_direct_tsd()) {
_pthread_setspecific_direct(k, value);
} else {
pthread_setspecific(k, value);
}
}
- 调用autoreleaseNoPage,创建page
第一次调用push的时候,还没有page,所以通过hotPage()是获取不到的,此时会调用autoreleaseNoPage创建page:
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
第一次调用这个方法,haveEmptyPoolPlaceholder返回false,且传入的obj是POOL_BOUNDARY,所以代码会走到setEmptyPoolPlaceholder():
static inline id* setEmptyPoolPlaceholder()
{
assert(tls_get_direct(key) == nil);
tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
return EMPTY_POOL_PLACEHOLDER;
}
这个方法调用后,再次调用autoreleaseNoPage时就会进入第一个if条件中,接着就会调用下面的方法创建一个page并设为hotPage,接着向page中添加一个边界值POOL_BOUNDARY,最后再把将要autorelease的对象添加进来。
- page添加对象add(obj)
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
将obj添加到page中,next向后移一位。
- autoreleaseFullPage
每个page只要4096字节,page存储满后调用autoreleaseFullPage新建一个page
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child; //如果有子节点
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
autoreleaseFullPage的实现可以看出,如果当前page有子节点则将子节点设为hotPage,并将对象添加到子节点的page中。如果没有子节点则新建一个page,设为hotPage,并将对象添加到该page中。
现在我们看下当一个对象调用autorelease方法的实现是怎样的:
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
对象调用autorelease,通过函数autoreleaseFast将对象加入到AutoreleasePoolPage中;所以autorelease方法并不是释放对象的方法,是将对象加入到AutoreleasePool中等待释放;
那添加的到AutoreleasePool中的对象是怎样释放的呢?
Runloop注册了两个关于AutoreleasePool的观察者,回调函数都是_wrapRunLoopWithAutoreleasePoolHandler。如下,第一个observer的activites值为0x1,监听的是KCFRunloopEntry事件,其回调函数会调用_objc_autoreleasePoolPush() 创建自动释放池。第二个observer的activites值为0xa0,监听的是kCFRunLoopBeforeWaiting和kCFRunLoopExit;监听到kCFRunLoopBeforeWaiting在回调函数中调用_objc_autoreleasePoolPop和_objc_autoreleasePoolPush函数销毁旧的autoreleasePool并创建新的autoreleasePool;监听到kCFRunLoopExit在回调函数中调用_objc_autoreleasePoolPop函数释放autoreleasePool;
observers = (
"<CFRunLoopObserver 0x600001d48280 [0x10f59cb68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11218b1b1), context = <CFArray 0x6000022716e0 [0x10f59cb68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fca70800058>\n)}}"
"<CFRunLoopObserver 0x600001d481e0 [0x10f59cb68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11218b1b1), context = <CFArray 0x6000022716e0 [0x10f59cb68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fca70800058>\n)}}"
)
pop函数的实现:
static inline void pop(void *token) //token是栈顶指针
{
AutoreleasePoolPage *page;
id *stop;
#pragma mark --- part1----
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
#pragma mark --- part2----
page = pageForPointer(token);// 通过栈顶的地址找到对应的page
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
} else {
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
#pragma mark --- part3----
page->releaseUntil(stop);//
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {// 当前page容量小于一半时,kill掉child
page->child->kill();
}
else if (page->child->child) {// 当前page容量大于一半时,保留child,kill掉child的child
page->child->child->kill();
}
}
}
这个方法分为三个部分:
- part1:判断token是否是EMPTY_POOL_PLACEHOLDER,这是autoreleasepool首次push的时候返回的,最顶层的pop会执行这一部分;
- part2:在非ARC情况下,在新创建的线程中不使用autoreleasepool,直接调用autorelease方法时会出现token不等于POOL_BOUNDARY的情况。
- part3:对page中的对象调用release方法,接着kill child;
void kill()
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
kill函数将调用者的所有子节点置为nil;
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {//释放page中的autorelease对象,直到遇到POOL_BOUNDARY为止
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {// 如果本节点已经释放完
page = page->parent;// 释放父节点
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
releaseUntil函数从当前page的栈顶开始给autorelease对象发送release消息,当前page发送完毕,再向父节点中的autorelease对象发送release消息,直到首节点为止。