#自动释放池初探 修改main.m里的代码如下:
int main(int argc, const char * argv[]) { @autoreleasepool { } return 0;}复制代码
我们通过clang命令来看一下main.m的c++实现:
clang -rewrite-objc main.m -o main.cpp复制代码打开.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:
所以我们可以得出结论,自动释放池在初始化的时候执行了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 0x1000bd5c0objc[42414]: 506 releases pending.objc[42414]: [0x103003000] ................ PAGE (hot) (cold)objc[42414]: [0x103003038] ################ POOL 0x103003038objc[42414]: [0x103003040] 0x10061d340 NSObjectobjc[42414]: [0x103003048] 0x100604c70 NSObjectobjc[42414]: [0x103003050] 0x100606ba0 NSObjectobjc[42414]: [0x103003058] 0x1006079b0 NSObjectobjc[42414]: [0x103003060] 0x10061d150 NSObjectobjc[42414]: [0x103003068] 0x100614f70 NSObjectobjc[42414]: [0x103003070] 0x100615280 NSObjectobjc[42414]: [0x103003078] 0x100611700 NSObjectobjc[42414]: [0x103003080] 0x10060ff80 NSObjectobjc[42414]: [0x103003088] 0x10060cc30 NSObjectobjc[42414]: [0x103003090] 0x100609e30 NSObjectobjc[42414]: [0x103003098] 0x100609fd0 NSObjectobjc[42414]: [0x1030030a0] 0x100605a00 NSObjectobjc[42414]: [0x1030030a8] 0x100607810 NSObject...objc[42439]: [0x103002fe0] 0x10061e840 NSObjectobjc[42439]: [0x103002fe8] 0x10061e850 NSObjectobjc[42439]: [0x103002ff0] 0x10061e860 NSObjectobjc[42439]: [0x103002ff8] 0x10061e870 NSObjectobjc[42439]: [0x101005000] ................ PAGE (hot) objc[42439]: [0x101005038] 0x10061e880 NSObjectobjc[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,优先级最低,保证其释放池子发生在其他所有回调之后。