一、AutoreleasePool是什么
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。
看到这里有人可能会问,那到底延迟到什么时候执行呢?看完本文后,各位心中自然会有答案。
让我们写个Demo来验证一下:
#import <Foundation/Foundation.h>
// 生成两个全局weak变量用来观察实验对象
__weak NSString *weak_String;
__weak NSString *weak_StringAutorelease;
void createString(void) {
NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"]; // 创建常规对象
NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"]; // 创建autorelease对象
weak_String = string;
weak_StringAutorelease = stringAutorelease;
NSLog(@"------in the createString()------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
createString();
NSLog(@"------in the autoreleasepool------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
NSLog(@"------in the main()------");
NSLog(@"%@", weak_String);
NSLog(@"%@", weak_StringAutorelease);
return 0;
}
上述代码运行结果如下:
2016-04-01 16:21:46.961 AutoreleasePool[18401:708414] ------in the createString()------
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] Hello, World!
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] Hello, World! Autorelease
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] ------in the autoreleasepool------
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] (null)
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] Hello, World! Autorelease
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] ------in the main()------
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] (null)
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] (null)
Program ended with exit code: 0
首先在createString函数中创建了一个常规NSString对象和一个autorelease对象,然后分别赋值给两个weak全局变量用于观察目标对象。通过两个weak全局变量的打印结果我们可以看到,在createString
函数中两个对象都是正常存在的,出了createString
函数在autoreleasepool中,常规对象已经被释放,而autorelease对象依然存在。在autoreleasepool外,autorelease对象也被释放了。
通过运行结果,我们已经直观的了解了AutoreleasePool的作用,那么AutoreleasePool是如何实现的呢?
二、AutoreleasePool的实现
接下来我们将一步步探寻AutoreleasePool的底层实现:
首先我们调整上面的代码,只留下main函数和@autoreleasepool{}。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
然后在终端中使用clang -rewrite-objc
命令将上述OC代码重写成C++的实现。
搜索main我们可以看到main函数的实现重写成了如下代码:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
}
return 0;
}
通过对比可以发现,苹果通过声明一个__AtAutoreleasePool
类型的局部变量__autoreleasepool
实现了@autoreleasepool{}
。那么这一切是如何实现的呢?这就要看看__AtAutoreleasePool
的定义了:
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;
};
根据构造函数和析构函数的特点(自动局部变量的构造函数是在程序执行到声明这个对象的位置时调用的,而对应的析构函数是在程序执行到离开这个对象的作用域时调用),我们可以将上面两段代码简化成如下形式:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
至此,我们可以分析出,单个自动释放池的执行过程就是objc_autoreleasePoolPush()
—> [object autorelease]
—> objc_autoreleasePoolPop(void *)
。
看到这两个函数的前缀,我们就知道它们是runtime中的两个函数,接下来我们就打开runtime的源码,看看它们是如何实现的。文中使用的源码是objc4-680.tar.gz
三、AutoreleasePool源码解析
在runtime项目中搜索objc_autoreleasePoolPush
我们可以在objc/Source/NSObject.mm中的1749~1754行找到objc_autoreleasePoolPush()
函数的实现:
void *
objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
同样我们可以找到objc_autoreleasePoolPop()
函数的实现:
void
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
看到这里,我们发现这两个函数的实现都是调用了AutoreleasePoolPage
类中的方法。于是我们可以断定,AutoreleasePool的是通过AutoreleasePoolPage
类来实现的。
打开AutoreleasePoolPage
的定义我们可以看到它有下列属性:
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
通过这些属性,我们可以推断出,这是一个双向链表的节点,AutoreleasePool的内存结构就是一个双向链表。而源码上的注释也证实我们的推测:
/***********************************************************************
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_SENTINEL which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_SENTINEL 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.
**********************************************************************/
一个线程的autoreleasepool就是一个指针栈。
栈中存放的指针指向加入需要release的对象或者POOL_SENTINEL
(哨兵对象,用于分隔autoreleasepool)。
栈中指向POOL_SENTINEL
的指针就是autoreleasepool的一个标记。当autoreleasepool进行出栈操作,每一个比这个哨兵对象后进栈的对象都会release。
这个栈是由一个以page为节点双向链表组成,page根据需求进行增减。
autoreleasepool对应的线程存储了指向最新page(也就是最新添加autorelease对象的page)的指针。
通过阅读源码,我们可以分析出上述属性的作用:
-
magic
:用来校验 AutoreleasePoolPage 的结构是否完整; -
next
:指向栈顶,也就是最新入栈的autorelease对象的下一个位置; -
thread
:指向当前线程; -
parent
:指向父节点 -
child
:指向子节点 -
depth
:表示链表的深度,也就是链表节点的个数 -
hiwat
:表示high water mark(最高水位标记)
接下来我们看看实现AutoreleasePool的几个关键函数是如何实现的。为了方便起见,就直接将注释添加在代码中。
AutoreleasePoolPage::push()
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 区别调试模式
// Each autorelease pool starts on a new pool page.
// 调试模式下将新建一个链表节点,并将一个哨兵对象添加到链表栈中
dest = autoreleaseNewPage(POOL_SENTINEL);
} else {
dest = autoreleaseFast(POOL_SENTINEL); // 添加一个哨兵对象到自动释放池的链表栈中
}
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 获取最新的page(即链表上最新的节点)
if (page && !page->full()) {
return page->add(obj); // 在这个page存在且不满的情况下,直接将需要autorelease的对象加入栈中
} else if (page) {
return autoreleaseFullPage(obj, page); // 在这个page已经满了的情况下,新建一个page并将obj对象放入新的page(即入栈)
} else {
return autoreleaseNoPage(obj); // 在没有page的情况下,新建一个page并将obj对象放入新的page(即入栈)
}
}
autoreleaseFullPage(obj, page)
和autoreleaseNoPage(obj)
的区别在于autoreleaseFullPage(obj, page)
会将当前page的child指向新建的page,而autoreleaseNoPage(obj)
会在新建的page中先入栈一个POOL_SENTINEL
(哨兵对象),再将obj入栈。
id *add(id obj) // 入栈操作
{
assert(!full());
unprotect(); // 解除保护
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj; // 将obj入栈到栈顶并重新定位栈顶
protect(); // 添加?;? return ret;
}
AutoreleasePoolPage::pop(ctxt);
static inline void pop(void *token) // token指针指向栈顶的地址
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token); // 通过栈顶的地址找到对应的page
stop = (id *)token;
if (DebugPoolAllocation && *stop != POOL_SENTINEL) {
// This check is not valid with DebugPoolAllocation off
// after an autorelease with a pool page but no pool in place.
_objc_fatal("invalid or prematurely-freed autorelease pool %p; ",
token);
}
if (PrintPoolHiwat) printHiwat(); // 记录最高水位标记
page->releaseUntil(stop); // 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象
// 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();
}
}
}
AutoreleasePoolPage::autorelease((id)this);
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj); // 添加obj对象到自动释放池的链表栈中
assert(!dest || *dest == obj);
return obj;
}
autorelease
函数和push
函数一样,关键代码都是调用autoreleaseFast
函数向自动释放池的链表栈中添加一个对象,不过push
函数的入栈的是一个哨兵对象,而autorelease
函数入栈的是需要加入autoreleasepool
的对象。
四、补充
上面我们讲了AutoreleasePoolPage
的定义的属性中有一个hiwat
表示high water mark(最高水位标记)。那么什么是最高水位标记呢?这个概念可以用自然界中的潮汐现象来解释。大家都知道潮水是有涨有落的,涨潮涨到最高时的水位就是最高水位。放在代码中来说,autoreleasepool的内存结构是一个双向链表栈,会频繁的有入栈和出栈操作,栈中存放的对象也会有增有减,hiwat
就记录了入栈对象最多时候对象的个数。
static void printHiwat()
{
// Check and propagate high water mark
// Ignore high water marks under 256 to suppress noise.
AutoreleasePoolPage *p = hotPage(); // 获取最新的page
uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin()); // 计算栈中对象的数量
if (mark > p->hiwat && mark > 256) { // 当数量大于当前记录的最高水位标记且大
for( ; p; p = p->parent) { // 于256,更新每个page中的最高水位标记
p->unprotect();
p->hiwat = mark;
p->protect();
}
// ······
}
}
回到开头的问题:加入AutoreleasePool的对象,release将延迟到什么时候执行?
相信现在各位心里都已经有答案了。
参考链接
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/#jtss-tsina