剖析AutoReleasePool
本文所引用的资料如下
objc4-680源码
sunnyxx的黑幕背后的Autorelease
Clang官方文档
我是前言
Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[obj autorelease]来延迟内存的释放是一件简单自然的事,ARC下,我们甚至可以完全不知道Autorelease就能管理好内存。而在这背后,objc和编译器都帮我们做了哪些事呢,它们是如何协作来正确管理内存的呢?刨根问底,一起来探究下黑幕背后的Autorelease机制。
AutoReleasePool数据结构
谈释放一定得说它本身的数据结构以及实现方法才行.先看AutoReleasePool的数据结构.首先它是一个C++的对象.单个对象大小限制是4096字节(PAGE_MAX_SIZE->PAGE_SIZE->I386_PGBYTES -> 4096).本身是一个双向链表.主要属性是parent(父链表),child(子链表),thread(初始化时传入pthread_self(),当前线程.AutoReleasePool是与线程相关的).next(最后一个加入AutoReleasePool的对象位于AutoReleasePool对象的偏移量)
>>//NSObject.mm Line:581
class AutoreleasePoolPage
{
#define POOL_SENTINEL 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;
Autorelease对象什么时候释放?
这个问题拿来做面试题,问过很多人,没有几个能答对的。很多答案都是“当前作用域大括号结束时释放”,显然木有正确理解Autorelease机制。
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
-
RunLoop自动释放
在讲述执行Push和Pop时机的时候又需要说一下RunLoop相关的东西了.传送门
简述:App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
-
RunLoop手动释放
手动释放指的是在ARC环境下使用@autoreleasepool代码块包裹的autoRelease对象
@autoreleasepool {
//void *context = objc_autoreleasePoolPush()
Person *person = [[Person alloc] copyObject];
//内部返回前调用了autorelease方法
[person print1];
//objc_autoreleasePoolPop(context)
}
-
NSObject->autoRelease方法源码如下
简述流程
- 调用对象的autorelease方法
- 调用内部的rootAutorelease方法.
- 调用内部的rootAutorelease2方法
- 调用AutoreleasePoolPage::autorelease方法.传入self
- 调用autoreleaseFast方法,传入参数
- 在autoreleaseFast中通过hotPage方法获得当前使用的AutoreleasePoolPage对象.如果存在并且未满,则调用page的add方法将对象加入AutoreleasePoolPage的栈中.并移动next到下一位置.否为根据page对象是否存在执行不同的方法
如果page存在.则表示full返回true.则执行如下代码.循环遍历child属性到最后一页.然后创建一个新AutoreleasePoolPage对象.并将最后一个page对象设置为新创建对象的parent.然后调用setHotPage函数设置为当前page.并调用page->add方法将对象加入到page的栈中.否则走autoreleaseNoPage方法初始化page
//NSObject.mm Line:839 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); } } //NSObject.mm Line:851 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); } //NSObject.mm Line:869 static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { // No pool in place. assert(!hotPage()); if (obj != POOL_SENTINEL && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); // Push an autorelease pool boundary if it wasn't already requested. if (obj != POOL_SENTINEL) { page->add(POOL_SENTINEL); } // Push the requested object. return page->add(obj); }
-
AutoRelease相关代码如下
//NSObject.mm Line:2181
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
//NSObject.mm Line:596
inline id objc_object::rootAutorelease()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
//NSObject.mm Line:1093
__attribute__((noinline,used))
id objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
//NSObject.mm Line:910
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || *dest == obj);
return obj;
}
//NSObject.mm Line:839
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);
}
}
//NSObject.mm Line:812
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if (result) result->fastcheck();
return result;
}
-
Push以及Pop源码如下
//NSObject.mm Line:1764
void *_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
//NSObject.mm Line:1749
void *objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
//NSObject.mm Line:920
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_SENTINEL);
} else {
dest = autoreleaseFast(POOL_SENTINEL);
}
assert(*dest == POOL_SENTINEL);
return dest;
}
//NSObject.mm Line:1770
void _objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}
//NSObject.mm Line:1756
void objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
//NSObject.mm Line:933
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token);
stop = (id *)token;
if (DebugPoolAllocation && *stop != POOL_SENTINEL) {
// This check is not valid with DebugPoolAllocation off
// after an autorelease with a pool page but no pool in place.
_objc_fatal("invalid or prematurely-freed autorelease pool %p; ",
token);
}
if (PrintPoolHiwat) printHiwat();
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->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}