AutoreleasePool
-
本文只是作者对于看Runtime源码时AutoreleasePool的一个问题记录及解答,并没有提供完整流程
-
autoreleasepool是和线程绑定的,一个线程对应一个autoreleasepool。而runloop和线程也是一一对应的,所以…
class AutoreleasePoolPage
{
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
// 哨兵对象
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
// AutoreleasePoolPage的大小,通过宏定义,可以看到是4096字节
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic;//magic 用于对当前 AutoreleasePoolPage 完整性的校验
// 一个AutoreleasePoolPage中会存储多个对象
// next指向的是下一个AutoreleasePoolPage中下一个为空的内存地址(新来的对象会存储到next处)
id *next;
//(一个AutoreleasePoolPage属于一个线程,一个线程中可以有多个AutoreleasePoolPage)
pthread_t const thread;//保存了当前页所在的线程
// AutoreleasePoolPage是以双向链表的形式连接
// 指向前一个节点
AutoreleasePoolPage * const parent;
// 指向后一个节点
AutoreleasePoolPage *child;
uint32_t const depth; //page的深度,首次为0,以后每次初始化一个page都加1
uint32_t hiwat; //这个字段是high water的缩写,这个字段用来计算pool中最多存放的对象个数。在每次执行pop()的时候,会更新一下这个字段。
其中的注释也解释了AutoReleasePool的结构,如下56个字节存储以下信息,其他4096 - 56存注册到AutoReleasePool的对象信息
摘自@思锦_Leo 思锦_Leo
为什么有了ARC还要AutoreleasePool?
这个问题之前我也想过,搜了下,没有感觉回答满意的,也没找到苹果的官方回答,这里只能自给妄自推断一下。提到OC的RC,首先要横向对比一下Android的GC,GC的内存回收是集中式回收(定期回收),而RC的回收是伴随整个运行时的,所以android机器有种时“卡”时“流畅”的感觉,而iOS总体比较均匀,缺乏像GC的集中式回收内存的类似机制,所以猜测Pool的产生也是弥补RC的这一不足,在RC基础上进行内存优化的一种手段。
将对象添加到AutoreleasePoolPage中 add(id obj)
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
// next = obj; next++;
// 也就是将obj存放在next处,并将next指向下一个位置
*next++ = obj;
protect();
return ret;//next-1
}
objc_autoreleasePoolPop
AutoreleasePoolPage::pop(ctxt)
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);//获取当前 token 所在的 AutoreleasePoolPage
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 (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);//释放栈中的对象,直到 stop
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (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();
}
}
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
//**可以从这段代码看出objc_autoreleasePoolPop的参数atautoreleasepoolobj为objc_autoreleasePoolPush()返回的值**
pop里面有个token参数,经过查找,发现其实际为指向POOL_BOUNDARY的指针,也就是每次push,pop之间的抬头,这也就刚好印证了
push(1)
push(2)
push(3)
pop(3)
pop(2)
pop(1)
AutoreleasePoolPush
这是push 方法中调用的一个方法
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 {
// 如果page->child不为空,那么使用page->child
if (page->child) page = page->child;
// 否则的话,初始化一个新的AutoreleasePoolPage
else page = new AutoreleasePoolPage(page);
} while (page->full());
// 将找到的合适的page设置成hotPage
setHotPage(page);
// 将对象添加到hotPage中
return page->add(obj);
}
上面提到的判断if (page->child) page = page->child; 为什么要判断一下有没有孩子呢,按理来说应该没有孩子的啊
原因如下:在pop的时候killpage时,如果page没超过一半满,就把其所有child杀了,否则,从page->child->child
开始杀,留下了page->child,所以以上。。。
static inline void pop(void *token)
{
省略以上代码......
else if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
线程与AutoreleasePool
在主线程的NSRunLoop对象的每个event loop开始之前,系统会自动创建一个autoreleasingPool,并在event loop结束时drain
另外,NSAutoreleasePool 中还提到,每一个线程都会维护自己的 autoreleasepool 堆栈。换句话说 autoreleasepool 是与线程紧密相关的,每一个 autoreleasepool 只对应一个线程。
手动添加AutoreleasePool的情况
如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
如果你编写的循环中创建了大量的临时对象;
如果你创建了一个辅助线程。
什么时候释放
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
__weak NSArray *string_weak_ = nil;
- (void)viewDidLoad {
[super viewDidLoad];
NSArray __weak *string = [NSArray arrayWithObjects:@"123", nil];
string_weak_ = string;
NSLog(@"%@- 1", string_weak_); // 123
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"%@ - 2", string_weak_); //123
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"%@- 3", string_weak_);//null
}
然后猜测viewDidLoad viewWillAppear是在同一个RunLoop中,而viewDidAppear可能在另一个线程
其他Autorelease相关知识点
使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
当然,在普通for循环和for in循环中没有,所以,还是新版的block版本枚举器更加方便。for循环中遍历产生大量autorelease变量时,就需要手加局部AutoreleasePool