自动释放池
自动释放池的数据结构就是一个双向链表。
首先可以看到main
函数:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
通过 clang
查看编译后的部分代码,全部代码请查看 main.cpp
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
......
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2f_kt02d2w10p55r2z7mn5gc7qw0000gn_T_main_904163_mi_0);
}
return 0;
}
通过main.cpp
文件可以看到,底层调用了两个方法objc_autoreleasePoolPush()
、objc_autoreleasePoolPop()
。
具体这两个方法底层做了哪些事情呢?这时候找到该方法所在的源文件 libobjc
objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
AutoreleasePoolPage
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MIN_SIZE; // size and alignment, power of 2
#endif
....
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
}
AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
magic_t const magic;//⽤来校验 AutoreleasePoolPage 的结构是否完整;
__unsafe_unretained id *next;//指向最新添加的 autoreleased 对象的下⼀个位置,初始化时指向
begin()
pthread_t const thread;//指向当前线程
AutoreleasePoolPage * const parent;//指向⽗结点,第⼀个结点的 parent 值为 nil
AutoreleasePoolPage *child;//指向⼦结点,最后⼀个结点的 child 值为 nil
uint32_t const depth;//代表深度,从 0 开始,往后递增 1
uint32_t hiwat;//代表 high water mark 最⼤⼊栈数量标记
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
接下来,通过代码测试一下:
从打印内容可以看到,page分为hot和cold,objc也被加入到了autoreleasepool中。
autorelease是如何加入的?
PUSH过程
static inline void *push()
{
id *dest;
if (slowpath(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;
}
autoreleaseFast
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
//判断page是否存在,是否已经满了,以及后续操作
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
autoreleaseFullPage
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);
}
autoreleaseNoPage
创建新的Page
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
ASSERT(!hotPage());
bool pushExtraBoundary = false;
......
// We are pushing an object or a non-placeholder'd pool.
// 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);
}
这里看一下AutoreleasePoolPageData
的参数:
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
beginz()
看到这里的this是AutoreleasePoolPage
这里的sizeof(*this)
指的是this所指向的内存空间的大小。
这里的56是怎么得出的呢?结构体占用内存的大小与它的成员变量有关。
AutoreleasePoolPage
继承自AutoreleasePoolPageData
,而父类的成员有如下内容:
magic_t const magic; //占16
__unsafe_unretained id *next; //8
pthread_t const thread; //8
AutoreleasePoolPage * const parent; //8
AutoreleasePoolPage *child; // 8
uint32_t const depth; // 4
uint32_t hiwat; // 4
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1;//静态变量存储在全局区,不占用结构体的内存
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4]; //4*4=16
}
因此,16+8+8+8+8+4+4=56 , 56是这么来的。
这里return返回的地址就是 autoreleasepool
成员变量之后的位置。
objc_thread_self()
获取当前线程
static inline pthread_t objc_thread_self()
{
return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
自动释放池创建和压栈过程示意图
自动释放池是分页的,每一页的大小是2^12,超过这个大小,即为满页,满页之后会创建新的一页。在整个自动释放池中只有一个哨兵对象。
POP过程
objc_autoreleasePoolPop
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
pop
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
面试题
这里以 alloc 、new、copy、mutablecopy
为前缀创建的对象,都不会被加进autoreleasePool
中。