对于autoreleasepool
一定不陌生,但内部结构是什么呢?
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
1、autoreleasepool结构
@autoreleasepool
做了什么操作呢?再次祭出clang
clang命令:
clang -rewrite-objc main.m -o main.cpp
int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool; }
return 0;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
- 在oc中
autoreleasepool在经过clang的编译之后
,底层的实现是一个__AtAutoreleasePool结构体
; - 构造函数调用
objc_autoreleasePoolPush()
,析构函数objc_autoreleasePoolPop(atautoreleasepoolobj)
;这三个点就是探索的入口; - autoreleasepool也是一个对象;
2、autoreleasePool结构体
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
//一页大小4098
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
static size_t const SIZE = PAGE_MAX_SIZE
// 构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
...
}
// 析构函数
~AutoreleasePoolPage()
{...}
}
-
thread_data_t
保存当前线程信息,可以看到自动释放池与线程是有关的; - 一页的大小是
4096字节
,后面会验证这部分; -
构造函数
中newParent=nil
也就是AutoreleasePoolPageData(begin(), objc_thread_self(),nil newParent, 0, 0)
2.1 AutoreleasePoolPageData 父类
/***********************************************************************
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
**********************************************************************/
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
//对照:`AutoreleasePoolPageData(begin(), objc_thread_self(),nil newParent, 0, 0)`
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)
{
}
};
autoreleasepool特点:
- 线程的自动释放池是一个指针堆栈。
- 栈里面的指针要么是对象指针,要么就是POOL_BOUNDARY,这里理解为边界、哨兵。
- 释放池的栈是以page为单位的双向链表结构
- 页的深度由depth标记。
AutoreleasePoolPageData参数说明
- _next:指向的是最新添加的 autoreleased 对象的下一个位置,初始化时指向
begin() 。 - thread:指向当前线程。
- parent:指向父节点,第一个节点为nil。
- child:直接点,最后一个节点为nil。
- depth:节点深度,默认是0.
- hiwat:代表最大入栈数量标记。
child和parent
就说明AutoreleasePoolPageData是一个双向链表
;
结构图:
-
销毁对象
:就是在autoreleasepool
中声明的对象,每次入栈一个对象_next就会往后移动8个字节
; - 一个释放池可能会存在多页(在内部对象非常多的情况下),最后一页会标记为
hotPage()
,其他页标记为coldPage()
;
2、autoreleasepool 入栈&出栈
在clang代码查看到的:* objc_autoreleasePoolPush()
,objc_autoreleasePoolPop(atautoreleasepoolobj)
2.1 objc_autoreleasePoolPush 入栈
源码调用流程,只看主分支代码:objc_autoreleasePoolPush -> AutoreleasePoolPage::push() -> autoreleaseFast(POOL_BOUNDARY) -> autoreleaseFast
POOL_BOUNDARY nil 哨兵对象
POOL_BOUNDARY在每一个autoreleasepool第一页的第一个对象插入,防止在释放时越界;
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
// 1. 如果不满,则直接插入
if (page && !page->full()) {
return page->add(obj);
} else if (page) { // 2. 如果满了,创建新页并将当前页传入建立关系
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
id *add(id obj)
{
id *ret = next; // faster than `return next-1` because of aliasing
// 对象赋值,并移动next
*next++ = obj;
return ret;
}
总体流程:
-
第一次 push 方法
是创建一个页,并设置为当前的焦点页(hotPage)
,并且把哨兵放进去做栈底
。 - 每次向自动释放池中添加对象时,如果
焦点页已满
,会递归子页去找到未满的页来入栈对象
; -
如果所有页都满了,会再创建一个页,并将上一个页的child 指向这个新的页,然后把新的页设置为焦点页
。
2.2 objc_autoreleasePoolPop 出栈
源码调用流程,只看主分支代码:objc_autoreleasePoolPop -> AutoreleasePoolPage::pop(ctxt) -> popPage
template
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
// 一口气释放页内所有对象
page->releaseUntil(stop);
// 释放页
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && 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();
}
}
}
2.2.1 kill 释放当前页
void kill()
{
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);
}
2.2.2 kill 释放页内对象
void releaseUntil(id *stop)
{
while (this->next != stop) {
// 获取当前页,当前页有可能是空的
AutoreleasePoolPage *page = hotPage();
//找到非空页
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) {
//// 如果不是哨兵对象则继续释放
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
for (int i = 0; i < count + 1; i++) {
objc_release(obj);
}
#else
objc_release(obj);
#endif
}
}
setHotPage(this);
}
- 释放时沿着parent这条路线向上释放,只到
释放到第一页的哨兵对象
结束; - 总体流程
- 遵循先进后出的原则,后进入的先释放,
- 先找焦点页的最大的next, next 逐个递减,倒着将对象一一释放,直到焦点页为空,
- 再沿着parent 找到上一个页,并设置为焦点页,重复第2步, 直到遇到哨兵对象停止。
- 然后倒着释放页,一个个释放。
补充:
_objc_autoreleasePollPrint()
- 可以通过该命令打印当前autoreleasepool中的情况;