AutoreleasePool
自动释放池,是 OC 中的一种内存自动回收机制
,可以将加入自动释放池中的对象的release 时机
延迟。当自动释放池作用域结束时,将池中的对象统一发送一次release
消息,当对象的引用计数为零时,对象就会被释放。
我们这次来探究一下AutoreleasePool
自动释放池的实现原理!
1. AutoreleasePool底层结构
在 main.m 编写如下函数:
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
通过clang -rewrite-objc main.m
指令,将 main.m 转化为main.cpp
文件,自动释放池部分代码如下:
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
// 构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
// 析构函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
// 成员变量
void * atautoreleasepoolobj;
};
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
上面的代码很清晰,自动释放池构造时,会调用objc_autoreleasePoolPush()
函数,销毁时会调用objc_autoreleasePoolPop()
函数。
对应 objc
中的源码实现为:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
void *
_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
void
_objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}
可以看到,自动释放池构造时调用AutoreleasePoolPage::push()
函数,析构时调用 AutoreleasePoolPage::pop(ctxt);
函数。下面也会从这两个函数为切入点进行分析。
在AutoreleasePoolPage
类实现的上方有一段注释,翻译如下:
- 一个线程的自动释放池就是一个存放对象指针的栈(
AutoreleasePool
的结构是由AutoreleasePoolPage
作为节点构成的双向链表
,而每个AutoreleasePoolPage
里面有一个存放对象指针的栈
)。 - 栈里面的每个指针要么是
等待autorelease的对象
,要么是POOL_BOUNDARY
自动释放池边界(实际为#define POOL_BOUNDARY nil
,同时也是next
的指向)。一个pool token
是指向POOL_BOUNDARY
的指针。
-AutoreleasePoolPage
会根据需要进行动态添加和删除。 -
hotPage
保存在当前线程中,当有新的autorelease 对象
添加进自动释放池时会被添加到hotPage
。
先大概理解一下,下面继续看下AutoreleasePoolPage
类:
//************宏定义************
#define PAGE_MIN_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096 /* bytes per 80386 page */
//************类定义************
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
是私有继承自AutoreleasePoolPageData
的类,并且页的大小为 4096
字节,保存的 autorelease 对象
的指针,每个指针占 8
个字节(后面会进行验证)。
我们继续看下AutoreleasePoolPageData
,是一个结构体:
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic; //用来校验AutoreleasePoolPage的结构是否完整
__unsafe_unretained id *next; // 作为游标指向栈顶最新 add 进来的 autorelease 对象的下一个位置
pthread_t const thread; // AutoreleasePool是按线程一一对应的,thread 是自动释放池所处的线程
AutoreleasePoolPage * const parent; // 指向前面的节点
AutoreleasePoolPage *child; // 指向后面的节点
uint32_t const depth; // 标记每个结点在链表中的深度,第一个节点为 0,后面依次递增
uint32_t hiwat;
// 构造函数
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)
{
}
};
从AutoreleasePoolPageData
结构体中可以看出,其包含构成双向链表的两个指针parent
和child
,所以说明自动释放池确实是一个双向链表
结构。
AutoreleasePoolPageData
结构体占用的内存大小为56
个字节(这个点在下面会体现到):
-magic
的类型是 magic_t
结构体,成员变量只有uint32_t m[4];
,占用 4 * 4 = 16
字节
-
next、thread 、parent、child
均占8
字节,即4 * 8 = 32
字节 - 属性
depth、hiwat
类型为uint32_t
,实际类型是unsigned int
类型,均占4
字节(即2 * 4 = 8
字节)
大概是下图这样的结构:
其中的
56
个字节存储的AutoreleasePoolPage的成员变量
,其他的区域存储加载到自动释放池的对象。当
next==begin()
时表示AutoreleasePoolPage为空,当next==end()
的时表示AutoreleasePoolPage已满。
大概了解了AutoreleasePool
的结构之后,我们就从objc_autoreleasePoolPush
方法开始进行分析。
2. objc_autoreleasePoolPush源码分析
objc_autoreleasePoolPush
方法,内部是调用了AutoreleasePoolPage::push()
方法,源码如下:
//入栈
static inline void *push()
{
id *dest;
// OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION,
// "halt when autorelease pools are popped out of order,
// and allow heap debuggers to track autorelease pools")
// 当自动释放池弹出顺序时停止,并允许堆调试器跟踪自动释放池
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.自动释放池从新池页面开始
// 创建一个新的自动释放池
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
// 压栈一个POOL_BOUNDARY,即压栈哨兵
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
从源码中可以看出,如果自动释放池不存在,则构建一个新的page
。push
函数的作用可以理解为,调用AutoreleasePoolPage::push
在当前线程的存储空间保存一个 EMPTY_POOL_PLACEHOLDER
。
autoreleaseFast
函数比autoreleaseNewPage
多了一个判断page
还没有满时就直接添加 obj
到 page
中的逻辑,剩下的调用autoreleaseFullPage
和 autoreleaseNoPage
是一样的。
2.1 autoreleaseNewPage函数
autoreleaseNewPage
函数源码如下:
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
// 获取当前操作的page
AutoreleasePoolPage *page = hotPage();
// 如果存在则压栈对象
if (page) return autoreleaseFullPage(obj, page);
// 如果不存在则创建页
else return autoreleaseNoPage(obj);
}
2.2 hotPage
获取当前的页,hotPage
是在线程中存储的,代码如下:
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
// 如果是一个空池,则返回nil,否则,返回当前线程的自动释放池
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
2.3 autoreleaseFullPage
当page
满了的时候,就会调用autoreleaseFullPage
方法,主要是构建新的AutoreleasePoolPage
,添加进双向链表
中,并把obj
添加进去,代码如下:
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.
// 如果 hotpage 满了,转到下一个未满的 page,如果不存在的话添加一个新的 page。
// 然后把 object 添加到新 page 里。
// page 必须是 hotPage
ASSERT(page == hotPage());
// page 满了,或者自动释放池按顺序弹出时暂停,并允许堆调试器跟踪自动释放池
// OPTION( DebugPoolAllocation,
// OBJC_DEBUG_POOL_ALLOCATION,
// "halt when autorelease pools are popped out of order,
// and allow heap debuggers to track autorelease pools")
// 自动释放池按顺序弹出时暂停,并允许堆调试器跟踪自动释放池
ASSERT(page->full() || DebugPoolAllocation);
// do while 循环里面分为两种情况
// 1. 沿着 child 往后走,如果能找到一个非满的 page,则可以把 obj 放进去
// 2. 如果 child 不存在或者所有的 child 都满了,
// 则构建一个新的 AutoreleasePoolPage 并拼接在 AutoreleasePool 的双向链表中,
// 并把 obj 添加进新 page 里面
do {
if (page->child) page = page->child;
// 构造方法时传进的page的child会指向新构建的page,也就是添加新构建的page到双向链表中
else page = new AutoreleasePoolPage(page);
} while (page->full());
// 设置 page 为 hotPage
setHotPage(page);
// 把 obj 添加进 page 里面,返回值是 next 之前指向的位置 (objc_object **)
return page->add(obj);
}
其中 page->add(obj)
是将obj 压入栈
中:
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
2.4 autoreleaseNoPage
autoreleaseNoPage
创建新页的方法,代码如下:
static __attribute__((noinline))
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
// "No page" 可能意味着没有构建任何池,或者只有一个 EMPTY_POOL_PLACEHOLDER 占位
// hotPage 不存在,否则执行断言
ASSERT(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// 如果线程里面存储的是 EMPTY_POOL_PLACEHOLDER
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// 我们正在将第二个池推入空的占位符池,或者将第一个对象推入空的占位符池。
// Before doing that, push a pool boundary on behalf of the
// pool that is currently represented by the empty placeholder.
// 在此之前,代表当前由空占位符表示的池来推动池边界
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// OPTION( DebugMissingPools, OBJC_DEBUG_MISSING_POOLS,
// "warn about autorelease with no pool in place, which may be a leak")
// 警告在没有自动释放池的情况下进行 autorelease,
// 这可能导致内存泄漏(可能是因为没有释放池,然后对象缺少一次 objc_release 执行,导致内存泄漏)
// 如果 obj 不为 nil 并且 DebugMissingPools。
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
// 我们正在没有自动释放池的情况下把一个对象往池里推,
// 并且打开了 environment 的 no-pool debugging,此时会在控制台给一个提示信息。
// 线程内连 EMPTY_POOL_PLACEHOLDER 都没有存储,并且如果 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",
objc_thread_self(), (void*)obj, object_getClassName(obj));
// obj 不为 nil,并且线程内连 EMPTY_POOL_PLACEHOLDER 都没有存储
// 执行 objc_autoreleaseNoPool,且它是个 hook 函数
objc_autoreleaseNoPool(obj);
// 返回 nil
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION,
// "halt when autorelease pools are popped out of order,
// and allow heap debuggers to track autorelease pools")
// 当自动释放池顺序弹出时暂停,并允许堆调试器跟踪自动释放池
// 如果 obj 为空,并且没有打开 DebugPoolAllocation
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// 在没有池的情况下,我们设置一个空池占位,并且不要求为池分配空间和调试。(空池占位只是一个 ((id*)1))
// Install and return the empty pool placeholder.
// 根据 key 在当前线程的存储空间内保存 EMPTY_POOL_PLACEHOLDER 占位
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// 构建非占位的池
// Install the first page.
// 构建自动释放池的第一个真正意义的 page
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
// 设置为 hotPage
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
// 代表先前占位符的池推边界。
// 如果之前有一个 EMPTY_POOL_PLACEHOLDER 在当前线程的存储空间里面占位的话
if (pushExtraBoundary) {
// 池边界前进一步
// 可以理解为把 next 指针往前推进了一步,并在 next 之前的指向下放了一个 nil
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
// 把 objc 放进自动释放池
return page->add(obj);
}
这个方法主要是在线程中创建一个EMPTY_POOL_PLACEHOLDER
占位,然后创建自动释放池的第一页,并且将哨兵对象
压进栈中。
其中AutoreleasePoolPage
的创建是通过其构造方法实现的,它的构造方法其实是AutoreleasePoolPageData
的构造方法:
//**********AutoreleasePoolPage构造方法**********
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),//开始存储的位置
objc_thread_self(),//传的是当前线程,当前线程时通过tls获取的
newParent,
newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
//this 表示 新建页面,将当前页面的子节点 赋值为新建页面
parent->child = this;
parent->protect();
}
protect();
}
//**********AutoreleasePoolPageData初始化方法**********
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)
{
}
上面 begin()
表示压栈的位置,即下一个要释放对象的压栈地址,其实就是页的首地址 + 56
,这个56
其实就是结构体AutoreleasePoolPageData
本身所占用的内存大小,在上面我们已经计算过了。
2.2 autoreleaseFast
代码实现如下:
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);
}
}
主要是判断当前页是否满了,如果没满,则直接添加,如果满了则开辟新页,如果当前页都没有,再进入autoreleaseNoPage
的流程。
2.3 自动释放池内存结构
在分析了objc_autoreleasePoolPush
之后,我们对于自动释放池有了个大概的认识,现在我们通过代码去验证下他的结构:
由于在ARC
模式下,是无法手动调用autorelease
,所以将Demo切换至MRC模式(Build Settings -> Objectice-C Automatic Reference Counting设置为NO)
:
定义如下代码:
//************打印自动释放池结构************
extern void _objc_autoreleasePoolPrint(void);
//************运行代码************
int main(int argc, const char * argv[]) {
@autoreleasepool {
//循环创建对象,并加入自动释放池
for (int i = 0; i < 5; i++) {
NSObject *objc = [[NSObject alloc] sutorelease];
}
//调用
_objc_autoreleasePoolPrint();
}
}
运行结果如下,发现是 6
个,但是我们实际上创建了 5
个对象啊,其中的POOL
表示哨兵,即边界,其目的是为了防止越界,也就是我们在上面autoreleaseNoPage
方法中添加的POOL_BOUNDARY
:
从上图中我们看到,page
的首地址是0x100817000
和 哨兵对象的地址0x100817038
相差0x38
,转换成十进制刚好是56
,也就是AutoreleasePoolPage本身所占用的大小
,想想上面的 begin()
方法,也是从首地址 + 56
开始存储的。
在一开始我们还提到 page
的大小为 4096
字节,自身占用了56
个字节,哨兵对象占用了8
个字节,所以除去哨兵对象还能存储 4032 / 8 = 504
个对象,我们将上述的测试代码的循环次数数据改为 505
,其内存结构如下,发现第一页满了,存储了504个
要释放的对象,第二页只存储了一个 ,说明是对的:
我们将数据改为505 + 506
,来验证第二页是否也是存储 505
个对象:
可以发现,第一页存储了504
个,第二页存储了 505
个,第三页存储了2
个。
所以通过上述测试,可以得出以下结论:
- 第一页可以存放
504
个对象,且只有第一页有哨兵对象,当第一页满了,就会开辟新的页 - 第二页开始,最多可以存放
505
个对象 - 一页的大小 =
505 * 8 + 56 = 4096
字节
自动释放池结构如下图所示:
2.4. autorelease底层分析
在 demo 中,我们通过 autorelease
方法,在MRC
的模式下,将对象压栈到自动释放池,会调用objc_autorelease
方法,源码如下:
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
//如果不是对象,则直接返回
if (!obj) return obj;
//如果是小对象,也直接返回
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
进入对象的autorelease
实现:
👇
inline id
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
//判断是否是自定义类
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
👇
inline id
objc_object::rootAutorelease()
{
//如果是小对象,直接返回
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
👇
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
👇
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
//autoreleaseFast 压栈操作
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
从这里看出,无论是压栈哨兵对象
,还是普通对象
,都会来到autoreleaseFast
方法,只是区别标识不同。
2.5 总结
autorelease
和 objc_autoreleasePush
的整体流程如下图所示:
3. objc_autoreleasePoolPop 源码分析
在objc_autoreleasePoolPop
方法中有个参数,在clang
分析时,发现传入的参数是push
压栈后返回的对象,即ctxt
,其目的是避免出栈混乱,防止将别的对象出栈:
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
// 弹出顶级 EMPTY_POOL_PLACEHOLDER 占位符池
// 取出 hotPage
page = hotPage();
if (!page) {
// 如果 hotPage 不存在,则表示目前就一 EMPTY_POOL_PLACEHOLDER,说明池还没有使用过
// Pool was never used. Clear the placeholder.
// Pool 从未使用过。清除占位符。
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool 是使用过了。正常弹出其内容。
// Pool pages remain allocated for re-use as usual.
// Pool pages 保持分配以照常使用.
// 第一个 page
page = coldPage();
// 把第一个 page 的 begin 赋值给 token
token = page->begin();
} else {
// token 转为 page
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);
}
}
// allowDebug 为 true
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
// 释放对象删除 page
return popPage<false>(token, page, stop);
}
进入popPage
源码,其中传入的allowDebug
为false
,则通过releaseUntil
出栈当前页stop
位置之前的所有对象,即向栈中的对象发送release
消息,直到遇到传入的哨兵对象:
//出栈页面
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//出栈当前操作页面对象
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
//特殊情况:调试丢失的自动释放池时删除pop(top)的所有内容
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();
}
}
}
进入releaseUntil
实现,主要是通过循环遍历,判断对象是否等于stop
,其目的是释放stop
之前的所有的对象:
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
// 循环从 next 开始,一直后退,直到 next 到达 stop
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
// 取得当前的 AutoreleasePoolPage
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
// fixme 我认为 “while” 可以是 “if”,但我无法证明
// 我觉得也是可以用 if 代替 while
// 一个 page 满了会生成一个新的 page 并链接为下一个 page,
// 所以从第一个 page 开始到 hotPage 的前一个page,应该都是满的
// 如果当前 page 已经空了,则往后退一步,把前一个 AutoreleasePoolPage 作为 hotPage
while (page->empty()) {
// 当前 page 已经空了,还没到 stop,
// 往后走
page = page->parent;
// 把 page 作为 hotPage
setHotPage(page);
}
// 可读可写
page->unprotect();
// next 后移一步,并用解引用符取出 objc_object * 赋值给 obj
id obj = *--page->next;
// 把 page->next 开始的 sizeof(*page->next) 个字节置为 SCRIBBLE
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
// 只可读
page->protect();
// 如果 obj 不为 nil,则执行 objc_release 操作
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
// 这里还是把 this 作为 hotPage,
// 可能从 stop 所在的 page 开始到 hotPage 这些 page 本来存放自动释放对象的位置都放的是 SCRIBBLE
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
// 保证从当前 page 的 child 开始,向后都是空 page
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
从最前面的page
开始一直向后移动直到到达stop
所在的 page
,并把经过的page
里保存的对象都执行一次 objc_release
操作,把之前每个存放 objc_object **
的空间都置为 SCRIBBLE
,每个page
的 next
都指向了该 page
的begin
。
release
做的事情是遍历释放保存的自动释放对象,而kill
做的事情是遍历对 AutoreleasePoolPage
执行 delete
操作:
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;
// 从当前 page 开始一直沿着 child 链往前走,直到 AutoreleasePool 的双向链表的最后一个 page
while (page->child) page = page->child;
// 临时变量(死亡指针)
AutoreleasePoolPage *deathptr;
// 是 do while 循环,所以会至少进行一次 delete,
// 即当前 page 也会被执行 delete(不同与上面的 release 操作,入参 stop 并不会执行 objc_release 操作)
do {
// 要执行 delete 的 page
deathptr = page;
// 记录前一个 page
page = page->parent;
// 如果当前 page 的 parent 存在的话,要把这个 parent 的 child 置为 nil
// 这个是链表算法题的经典操作
if (page) {
// 可读可写
page->unprotect();
// child 置为 nil
page->child = nil;
// 可写
page->protect();
}
// delete page
delete deathptr;
} while (deathptr != this);
}
从当前的page
开始,一直根据 child
链向前走直到 child
为空,把经过的page
全部执行delete
操作(包括当前page
)。
当pthread_exit
线程退出时,触发了_pthread_tsd_cleanup
,触发AutoreleasePoolPage
的tls_dealloc(void*)
,然后回收autorelease
对象:
static void tls_dealloc(void *p)
{
// # define EMPTY_POOL_PLACEHOLDER ((id*)1)
// 如果 p 是空占位池则 return
if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
// No objects or pool pages to clean up here.
// 这里没有 objects 或者 pages 需要清理
return;
}
// reinstate TLS value while we work
// 这里直接把 p 保存在 TLS 中作为 hotPage
setHotPage((AutoreleasePoolPage *)p);
if (AutoreleasePoolPage *page = coldPage()) {
// 如果 coldPage 存在(双向链表中的第一个 page)
// 这个调用的函数链超级长,最终实现的是把自动释放池里的所有自动释放对象都执行
// objc_release 然后所有的 page 执行 delete
if (!page->empty()) objc_autoreleasePoolPop(page->begin()); // pop all of the pools
// OPTION( DebugMissingPools,
// OBJC_DEBUG_MISSING_POOLS,
// "warn about autorelease with no pool in place, which may be a leak")
// 警告没有池的自动释放,这可能是泄漏
// OPTION( DebugPoolAllocation,
// OBJC_DEBUG_POOL_ALLOCATION,
// "halt when autorelease pools are popped out of order,
// and allow heap debuggers to track autorelease pools")
// 当自动释放池顺序弹出时暂停,并允许堆调试器跟踪自动释放池
if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
// pop() killed the pages already
} else {
// 从 page 开始一直沿着 child 向前把所有的 page 执行 delete
// kill 只处理 page,不处理 autorelease 对象
page->kill(); // free all of the pages
}
}
// clear TLS value so TLS destruction doesn't loop
// 清除 TLS 值,以便 TLS 销毁不会循环
// 把 hotPage 置为 nil
// static pthread_key_t const key = AUTORELEASE_POOL_KEY;
// tls_set_direct(key, (void *)page);
// 把 key 置为 nil
setHotPage(nil);
}
综上所述,objc_autoreleasePoolPop
出栈的流程如下所示:
4. AutoreleasePool和RunLoop
一般很少会将自动释放池
和RunLoop
联系起来,但是如果打印[NSRunLoop currentRunLoop]
结果中会发现和自动释放池相关的回调。
<CFRunLoopObserver 0x6000024246e0 [0x7fff8062ce20]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
<CFRunLoopObserver 0x600002424640 [0x7fff8062ce20]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
即App启动后,苹果会给RunLoop
注册很多个observers
,其中有两个是跟自动释放池相关的,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
。
第一个observer监听的是activities=0x1(kCFRunLoopEntry)
,也就是在即将进入loop时,其回调会调用_objc_autoreleasePoolPush()
创建自动释放池;
第二个observer监听的是activities = 0xa0(kCFRunLoopBeforeWaiting | kCFRunLoopExit)
,
即监听的是准备进入睡眠和即将退出loop两个事件。在准备进入睡眠之前,因为睡眠可能时间很长,所以为了不占用资源先调用_objc_autoreleasePoolPop()
释放旧的释放池,并调用_objc_autoreleasePoolPush()
创建新建一个新的,用来装载被唤醒后要处理的事件对象;在最后即将退出loop时则会_objc_autoreleasePoolPop()
释放池子。