本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !
自动释放池是什么嘞 ?
自动释放池:即@autoreleasepool
,通过AutoreleasePoolPage来管理调用了autorelease
方法的对象,把该对象在合适的时机释放掉,这就是自动释放池
自动释放池的主要底层数据结构是:__AtAutoreleasePool
、AutoreleasePoolPage
release 和 drain的区别
当我们向自动释放池 pool 发送 release 消息,将会向池中临时对象发送一条 release 消息,并且自身也会被销毁
向它自动释放池发送drain消息时,将会向池中临时对象发送一条release消息同时在支持 GC 的系统中(Mac)可以引起 GC 回收操作,而 release 不可以
官方解释
release:清空自动释放池且把自动释放池对象释放。(ARC)
drain:清空自动释放池
- 原理:
define POOL_BOUNDARY nil
;nil值为0 ,是一个哨兵对象
假设还没有创建AutoreleasePoolPage,
从第一个autorelease对象开始
调用push方法会将一个
POOL_BOUNDARY入栈(不是栈空间,而是数据结构中的栈)
,并且返回POOL_BOUNDARY存储的内存地址调用push方法开始存放
第一个autorelease对象的地址
,返回存储的地址调用pop方法时传入一个
POOLBOUNDARY的内存地址
,会从最后一个入栈的对象开始发送release消息
,直到遇到这个POOL_BOUNDARYid
*next 是查询指针,指向下一个可以用来存放autorelease对象地址的区域,
POOL_BOUNDARY
是一个标记,代表一个自动释放池和另一个自动释放池的边界,所以存取都需要
@autoreleasepool 最终会在大括号的开始和结束生成以下两行代码:
@autoreleasepool {
//添加
atautoreleasepoolobj = objc_autoreleasePoolPush();
实现了autorelease的对象
//推出
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
直观的代码逻辑展示
@autoreleasepool {
atautoreleasepoolobj = objc_autoreleasePoolPush();
@autoreleasepool {
atautoreleasepoolobj = objc_autoreleasePoolPush();
@autoreleasepool {
atautoreleasepoolobj = objc_autoreleasePoolPush();
for ( 循环 ) {
@autoreleasepool ........
}
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
从runtime源码中找实现
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
push()
id *autoreleaseNewPage(id obj)
{
//创建
AutoreleasePoolPage *page = hotPage();
//把需要加入的对象添加(page->add(obj))进来
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {//当前没有pool page
// Each autorelease pool starts on a new pool page.
//传入POOL_BOUNDARY,新创建一个pool page并把obj地址添加进去
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {//直接传入POOL_BOUNDARY,并把obj地址添加进去
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
pop()
static inline void
pop(void *token)//token:POOL_BOUNDARY的地址
{
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 {
传入POOL_BOUNDARY的地址,找到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);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY返回
return popPage<false>(token, page, stop);
}
AutoreleasePoolPage
结构
每个AutoreleasePoolPage对象占用
4096字节
内存,除了用来存储它内部成员变量占用56字节
外,剩下的空间用来存放autorelease对象的地址
如果内存不够了,就会再创建一个新的AutoreleasePoolPage对象接着存所有的AutoreleasePoolPage对象是通过
双向链表
的形式连接在一起,第一页的上一页是0,最后一页的下一页是0
双向链表:第一个对象可以通过指针一个一个找到最后一个对象,最后一个对象也可以通过指针一个一个找到第一个对象,这种结构的链表被认为是双向链表;
缘分一道桥
设计思路:
一条锁链搭建的吊桥,两条粗壮的锁链;假设建造过程是这样的:一条从第一块桥板的左边穿过,一直到最后一块木板到达对岸,另一条从最后一块桥板开始往回穿过,一直到回到第一块桥板的右边;这个过程不就像极了双向链表嘛 😁😁😁😁😁😁😁😁
自动释放池与RunLoop有啥关系 ?
先看看主线程的Runloop做了什么
RunLoop活动状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), == 1
kCFRunLoopBeforeTimers = (1UL << 1), == 2
kCFRunLoopBeforeSources = (1UL << 2), == 4
kCFRunLoopBeforeWaiting = (1UL << 5), == 32
kCFRunLoopAfterWaiting = (1UL << 6), == 64
kCFRunLoopExit = (1UL << 7), == 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
/*
kCFRunLoopEntry push
<CFRunLoopObserver>{
valid = Yes,
activities = 0x1, 活动状态值 0x1 = 1 --> kCFRunLoopEntry
repeats = Yes,
order = -2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = {type = mutable-small, count = 1, values = (t0 : <0x7fd0bf802048>)}
}
kCFRunLoopBeforeWaiting | kCFRunLoopExit
kCFRunLoopBeforeWaiting pop、push
kCFRunLoopExit pop
<CFRunLoopObserver>{
valid = Yes,
活动状态值 0xa0 == 160 = 32 + 128 ; --> kCFRunLoopBeforeWaiting | kCFRunLoopExit
activities = 0xa0,
repeats = Yes,
order = 2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = {type = mutable-small, count = 1, values = (t0 : <0x7fd0bf802048>)
}
}
梳理一下:
activities = 0x1
活动状态值 0x1 = 1 --> kCFRunLoopEntry
activities= 0xa0
活动状态值 0xa0 == 160 = 32 + 128 ; --> kCFRunLoopBeforeWaiting | kCFRunLoopExit
kCFRunLoopEntry
: 开始进入runloop
kCFRunLoopBeforeWaiting
:runloop开始休眠
kCFRunLoopExit
:runloop退出
- 主线程的
runloop
中注册了2个Observer
(监听器) - 第1个Observer:
- 监听
kCFRunLoopEntry
事件,触发就会调用objc_autoreleasePoolPush()函数
- 监听
- 第2个Observer:
- 监听
kCFRunLoopBeforeWaiting
事件,触发就会调用objc_autoreleasePoolPop()和objc_autoreleasePoolPush()函数
- 监听
kCFRunLoopExit
事件,触发就会调用objc_autoreleasePoolPop()
- 监听
看一行代码来对其进行分析
XYHPerson *person = [[[XYHPerson alloc] init] autorelease];
- 这个Person什么时候调用release,是由RunLoop来控制的
- 它可能是在某次RunLoop循环中 ,在RunLoop休眠或退出之前调用了release
开发中实现的对象调用atuorelease方法会加入到
main函数
中的@atuoreleasepool
吗 ?
不会,main函数中@atuoreleasepool
是专门为main函数使用的;不会用作其他用途;
开发中实现的对象调用atuorelease
方法只会加入当前自动释放池,如果当前没有自动释放池,会新创建一个添加