小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
自动释放池autoreleasepool
找到工程的main函数,按照管理xcRun一下,生成.cpp文件
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2f_kt02d2w10p55r2z7mn5gc7qw0000gn_T_main_904163_mi_0);
}
return 0;
}
__AtAutoreleasePool __autoreleasepool
= @autoreleasepool
我们发现这是一个结构体,里面含有一个构造函数和一个析构函数。也就是说在这个自动释放池自动调用了objc_autoreleasePoolPush()
,作用域结束之后自动调用了objc_autoreleasePoolPop
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
打开libObjc
源码搜索objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
自动释放池的结构
自动释放池是一个双向链表。AutoreleasePoolPage
继承于AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
// ...
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
// ...
};
magic
⽤来校验AutoreleasePoolPage的结构是否完整;next
指向最新添加的autoreleased对象的下⼀个位置,初始化时指向begin();thread
指向当前线程;parent
指向⽗结点,第⼀个结点的parent值为nil;child
指向⼦结点,最后⼀个结点的child值为nil;depth
代表深度,从0开始,往后递增1;hiwat
代表highwatermark最⼤⼊栈数量标记
首先修改配置在MRC下
_objc_autoreleasePoolPrint
Apple给出的一个可以打印autoreleasePool
内容的函数
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
// class_data_bits_t
NSObject *obj = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
return 0;
}
这里为什么有两个对象需要释放呢?
自动释放池的创建和压栈
我们需要查看下AutoreleasePoolPage::push
调用发生了什么
autoreleaseFast
-> autoreleaseNoPage
(第一次创建走这里,创建page) ->AutoreleasePoolPage()
-> page->add(POOL_BOUNDARY)
-> page->add(obj)
,在构造函数AutoreleasePoolPage
的时候,可以打印出自动释放池成员变量的大小
所以对象压栈是在56位之后才开始
第一个对象:**[0x10200e038] ################ POOL 0x10200e038**
是边界也就是之前说的哨兵
第二个对象:**[0x10200e040] 0x10104fb00 NSObject**
是NSObject
既然自动释放池是分页的结构,那么一页的大小是多少
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL 0
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MIN_SIZE; // size and alignment, power of 2 = 4096
#endif
//...
}
PAGE_MIN_SIZE
= 1 << PAGE_MIN_SHIFT
= 1 << 12
= 2^12
= 4096
= 4kb
既然我们知道了一页的大小,那么我们for循环下,看下创建新的page的时候有什么不同?
通过打印的日志我们可以知道:在新的页面中并没有加入所谓的哨兵对象。
调用objc_autoreleasePoolPop
出栈,AutoreleasePoolPage::pop(ctxt)
-> popPage
// 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
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();
}
}
首先找到parent
然后kill掉自身,并把parent
置为setHotPage
来到kill()
函数里面是do while
循环来置空,总之加入在自动释放池被管理的对象,当接收到pop指令的时候,会把压栈进来的对象都pop出去,当发现到只有哨兵对象的时候,就表示page为空。
临时变量什么时候释放
在出了自动释放池的作用域之外就会释放
自动释放池能否嵌套使用
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
NSObject *obj = [[[NSObject alloc] init] autorelease];
@autoreleasepool {
NSLog(@"嵌套的自动释放池");
NSObject *obj1 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
_objc_autoreleasePoolPrint();
}
return 0;
}
一个自动释放池有一个哨兵对象,自动释放池可以嵌套使用。
自动释放池的拓展
以上都是在MRC情况下的调试,在MRC下如果不加autorelease
的对象是不会自动添加到自动释放池的
在实际的开发中,我们都是在ARC环境下来开发的,所以我们把配置切回到ARC环境下,结果发现在ARC创建的NSObject并不会加入到自动释放池。为什么呢?
原因:alloc、new、 copy、 mutableCopy
在LLVM编译期间不会加入到自动释放池,这个可以自己测试下。