AutoReleasePool 自动释放池
自动释放池是OC中的一种内存自动回收机制,它可以将加入AutoreleasePool
中的变量release
的时机延迟,简单来说,就是当创建一个对象,在正常情况下,变量会在超出其作用域的时立即release
。如果将对象加入到了自动释放池中,这个对象并不会立即释放,会等到runloop休眠/超出autoreleasepool作用域{}
之后才会被释放。
image.png
- 从程序启动到加载完成,主线程对应的
runLoop
会处于休眠状态,等待用户交互来唤醒runloop - 用户每一次交互都会启动一次
runLoop
,用于处理用户的所有点击、触摸事件等 - runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中
- 在一次完整的runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池
原理探索
首先只声明一个AutoReleasePool
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
进行clang -rewrite-objc main.m -o main.cpp
,发现原本的@autoreleasepool
没有了,取而代之的是__AtAutoreleasePool
结构体
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
#pragma clang assume_nonnull end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
这个结构体是由__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); 构造函数
和~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} 析构函数
组成,所以自动释放池的本事是个对象
结构探索
在源码中搜索objc_autoreleasePoolPush
方法
image.png
进入AutoreleasePoolPage
image.png
通过AutoreleasePoolPage
的注释得知
- 线程的自动释放池是指针的堆栈
- 每个指针都是要释放的对象,或者是POOL_BOUNDARY,它是自动释放池的边界。
- 堆栈被分成一个双向链接的页面列表, 页面已添加对象并根据需要删除
- 线程本地存储指向新自动释放的热点页面对象被存储
接下来看下AutoreleasePoolPageData
实现
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct AutoreleasePoolEntry {
uintptr_t ptr: 48;
uintptr_t count: 16;
static const uintptr_t maxCount = 65535; // 2^16 - 1
};
static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
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
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)
{
}
};
- magic ⽤来校验AutoreleasePoolPage的结构是否完整;
- next 指向最新添加的autoreleased对象的下⼀个位置,初始化时指向begin()
- thread 指向当前线程
- parent 指向⽗结点,第⼀个结点的parent值为nil
- child 指向⼦结点,最后⼀个结点的child值为nil
- depth 代表深度,从0开始,往后递增1
- hiwat 代表high water mark最⼤⼊栈数量标记
源码实现
查看压栈实现
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;
}
在release
模式下会调用autoreleaseFast
方法并传入POOL_BOUNDARY
哨兵对象,进入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);
}
}
- 获取当前页面,如果当前页面存在且没有存满,则向当前页中存入对象
- 当前页存在,但是当前页面已经存满,则调用
autoreleaseFullPage
方法 - 当前页不存在,调用
autoreleaseNoPage
查看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 while循环当page页满了时,执行
do {
// 如果当前页存在子页面,将page设置为子页面
if (page->child) page = page->child;
// 如果不存在则 重新创建一个页面
else page = new AutoreleasePoolPage(page);
} while (page->full());
// 将page页设置为当前页
setHotPage(page);
// 并将对象添加到page中
return page->add(obj);
}
查看autoreleaseNoPage
方法
image.png
新创建的
AutoreleasePoolPage
首先添加哨兵对象
,然后插入obj
,接下来看下AutoreleasePoolPage
的构造函数
image.png
由上图可知
AutoreleasePoolPage
是通过AutoreleasePoolPageData
进行初始化的,AutoreleasePoolPageData的属性占用56
字节
image.png
- 一页是总大小是4096字节,每页的固定属性占用56字节
- 只有第一页有哨兵对象,所以第一页做多存储504个对象,其他的存储505个对象
image.png
-
AutoreleasePool
是由多个AutoreleasePoolPage
以双向链表的形式链接起来 - 在创建自动释放池时,先把创建的页面设置为
hotPage
,并在AutoreleasePoolPage
中设置一个边界,当有对象调用autorelease
时,会将对象放入AutoreleasePoolPage
中 - 当
hotPage
加满时,会创建一个新的page,然后用双向链表链接起来,并把初始化的一页设置为hotPage,当自动释放池pop时,从最下面依次往上pop,调用每个对象的release方法,直到遇到哨兵对象
【注意点】
哪些对象可以放入自动释放池
- 在MRC下调用
autorelease
会加入到自动释放池 - 在ARC下,使用alloc, init,copy等方法创建的对象,不会加入到
自动释放池
在自动释放池中,对象release
而非销毁