autoreleasepool底层探索
自动释放池初探
修改main.m里的代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
我们通过clang命令来看一下main.m的c++实现:
clang -rewrite-objc main.m -o main.cpp
image.png
打开.cpp查看,可以看到,被编译成这样,产生了一个__AtAutoreleasePool类型的临时对象:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool()
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool()
{
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
__AtAutoreleasePool里面有两个函数, __AtAutoreleasePool() 和 ~__AtAutoreleasePool() ,c++中 结构体名的函数表示初始化时调用, ~+结构体名的函数会在析构时调用。
来看一个例子:自定义一个结构体:(为了支持c++编译,需要把main.m改为main.mm),然后来声明一个对象,查看log:
image.png
所以我们可以得出结论,自动释放池在初始化的时候执行了objc_autoreleasePoolPush函数,在析构的时候执行了objc_autoreleasePoolPop函数。
void * _objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
void _objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}
AutoreleasePoolPage::push():::是一个属性函数,说明AutoreleasePoolPage是一个结构体对象,来查看它的数据结构(为了便于理解,只列出其属性和我们需要查看的函数):
/**
struct magic_t {
# define EMPTY_POOL_PLACEHOLDER ((id*)1) //空占位符
# define POOL_BOUNDARY nil //边界符
static const uint32_t M0 = 0xA1A1A1A1;
#define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];
*/
magic_t const magic; // 4个uint32_t 占16位
id *next; //8位
pthread_t const thread; //8位
AutoreleasePoolPage * const parent; //8位 指向下一个page
AutoreleasePoolPage *child; //8位 指向上一个page
uint32_t const depth; //4位
uint32_t hiwat; //4位
static void * operator new(size_t size) {
return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
我们来算一下这些属性占了多少位:入上图加起来可以得到一个数字:56。
接着往下看new函数,创建时的SIZE根据宏定义我们一步步寻找:得到的数字为:4096,即一个AutoreleasePoolPage所能容纳的大小
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
#define PAGE_MAX_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096 /* bytes per 80386 page */
什么时候使用autoreleasepool ?
- 大量的临时变量;
- 某些非UI操作;
- 自己创建的辅助线程等等。
要进行自动释放,第一步肯定是要先存进去,这样才能进行释放,每一个page可以容纳4096个字节,而自己本身占了56个,所以每一个page可以容纳的最多对象数为:(4096-56)/ 8 = 505个。
继续进行代码测试,并通过void_objc_autoreleasePoolPrint来打印出autoreleasepool的信息(void_objc_autoreleasePoolPrint为系统函数,这里把它挪出来方便调试):
extern void_objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0; i < 505; i++) {
NSObject * obj = [[NSObject alloc] init];
void_objc_autoreleasePoolPrint();
}
}
return 0;
}
objc[42414]: AUTORELEASE POOLS for thread 0x1000bd5c0
objc[42414]: 506 releases pending.
objc[42414]: [0x103003000] ................ PAGE (hot) (cold)
objc[42414]: [0x103003038] ################ POOL 0x103003038
objc[42414]: [0x103003040] 0x10061d340 NSObject
objc[42414]: [0x103003048] 0x100604c70 NSObject
objc[42414]: [0x103003050] 0x100606ba0 NSObject
objc[42414]: [0x103003058] 0x1006079b0 NSObject
objc[42414]: [0x103003060] 0x10061d150 NSObject
objc[42414]: [0x103003068] 0x100614f70 NSObject
objc[42414]: [0x103003070] 0x100615280 NSObject
objc[42414]: [0x103003078] 0x100611700 NSObject
objc[42414]: [0x103003080] 0x10060ff80 NSObject
objc[42414]: [0x103003088] 0x10060cc30 NSObject
objc[42414]: [0x103003090] 0x100609e30 NSObject
objc[42414]: [0x103003098] 0x100609fd0 NSObject
objc[42414]: [0x1030030a0] 0x100605a00 NSObject
objc[42414]: [0x1030030a8] 0x100607810 NSObject
...
objc[42439]: [0x103002fe0] 0x10061e840 NSObject
objc[42439]: [0x103002fe8] 0x10061e850 NSObject
objc[42439]: [0x103002ff0] 0x10061e860 NSObject
objc[42439]: [0x103002ff8] 0x10061e870 NSObject
objc[42439]: [0x101005000] ................ PAGE (hot)
objc[42439]: [0x101005038] 0x10061e880 NSObject
objc[42439]: ##############
Program ended with exit code: 0
发现log信息里有两个page,并且第一个page的状态是从hot->cold ,第二个page的状态是hot,说明此时有两个page,第一个页面已经满了并置为cold状态,第二个page当前是激活状态(hot)。
但是这个为什么跟刚才算出来的不一样呢?505个应该是正好一页就够用了,为什么有个第二页呢?这是因为在添加第一个对象的时候有一个边界符:POOL_BOUNDARY,边界符的作用就是做一个标记,比如删除的时候,删到什么时候为止呢?到了边界符就停止删除操作。
autoreleasepool源码解析
autoreleasepool是由若干个AutoreleasePoolPage以双向链表的形式组合而成的栈结构(分别对应结构中的parent指针和child指针)
objc_autoreleasePoolPush
接着AutoreleasePoolPage::push()进行源码分析,跳入push:
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
//区别DEBUG模式
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
//直接从边界符开始进行压栈操作
//// 添加一个哨兵对象到自动释放池的链表栈中
dest = autoreleaseFast(POOL_BOUNDARY);
}
return dest;
}
autoreleaseFast
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); //取到当前处于hot状态的page
if (page && !page->full()) { //取到了并且没满 直接add进去
return page->add(obj);
} else if (page) { //取到了 但是满了
return autoreleaseFullPage(obj, page);
} else { //没有取到
return autoreleaseNoPage(obj);
}
}
autoreleaseFullPage
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
//一个while循环,递归查找page->child,如果找到就把它设置成**hot**状态,没找到就**new**一个,最终通过page->add(obj)添加obj
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0)
{
if (parent) {
parent->check();
assert(!parent->child);
parent->unprotect();
parent->child = this; //让它的parent的child指向自己,保持双向指向
parent->protect();
}
protect();
}
//最终执行page->add(obj);
id *add(id obj)
{
unprotect();
id *ret = next; // 获取当前的next指针
*next++ = obj; //让next指向obj 并且next++(先引用,再增加)
protect();
return ret;
}
autoreleaseNoPage
id *autoreleaseNoPage(id obj)
{
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
//判断是不是已经有了占位符
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
//出现异常
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
//设置占位符
return setEmptyPoolPlaceholder();
}
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY); //开始的时候先add边界符POOL_BOUNDARY
}
return page->add(obj); //最终执行page->add
}
总结一下:
- 没有page的时候,创建第一个page,并且add一个边界符;
- 开始正常add对象;
- page add满了后,创建一个新的page,并设置为hot,此时parent变为cold
objc_autoreleasePoolPop
static inline void pop(void *token) // token指针指向栈顶的地址
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token); // 通过栈顶的地址找到对应的page
page->releaseUntil(stop); // 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象,这一步会删除掉每个page里的每个对象
// 删除所有空page
if (DebugPoolAllocation && page->empty()) {
AutoreleasePoolPage *parent = page->parent;
page->kill(); //杀掉自己
setHotPage(parent); //将parent置为hot
} else if (DebugMissingPools && page->empty() && !page->parent) {
//page已经没有parent了,直接杀死
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();
}
}
}
void releaseUntil(id *stop)
{
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
while (page->empty()) {
page = page->parent; //page里的对象已经全部删了,将page指向parent,并置为hot
setHotPage(page);
}
page->unprotect();
id obj = *--page->next; //获取到要释放的对象
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj); //向page中每一个对象发送release
}
}
setHotPage(this);
}
//page kill操作
void kill()
{
AutoreleasePoolPage *page = this;
while (page->child) page = page->child; //递归找到最下面一层的page
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent; //指向parent
if (page) {
page->unprotect();
page->child = nil; //删掉自己
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
总结一下:
- 删除page里的每一个对象,然后将page指向parent并置为hot,进行递归
- 删除所有空的page
所以,出了autoreleasepool的作用域空间,就会释放掉所有的对象。
autoreleasepool与runloop
苹果在主线程 RunLoop 里注册了两个 Observer:
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠) 和 Exit(即将退出Loop),
BeforeWaiting(准备进入睡眠)时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。