Autoreleasepool相关的内容是在面试中比较容易被问到的。以前呢,谈到Autoreleasepool只能粗浅的了解到自动释放池与内存的管理有关,具体是怎么样来管理和释放对象,并无深刻的学习,本文是笔者在深刻学习Autoreleasepool以后的总结和心得,但愿对你们有帮助。git
首先咱们从main函数开始,main函数是咱们应用的入口。github
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
能够看出,咱们整个iOS应用都是包含在一个自动释放池中。
并且在如今的ARC环境中,自动释放池的用法就是这样子 @autoreleasepool {}
。面试
这个@autoreleasepool{}
你们都会用,咱们的代码直接写在这个大括号内便可。咱们代码中的对象是怎样加到自动释放池中的,最后又是怎么样被释放的呢 ?less
咱们要先知道这个@autoreleasepool
究竟是什么。函数
从网上的一些博客中能够学到的,在命令行使用clang -rewrite-objc main.m
让编译器从新改写main函数所在的这个文件。固然了这一步我并无操做,直接“盗用”了你们的结果。oop
从上图中能够看出,@autoreleasepool
被转换为了一个__AtAutoreleasePool
结构体。而后经过在main.cpp中查找找到这个结构体的定义。学习
struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj; };
这个结构体在初始化是调用objc_autoreleasePoolPush()
,在析构时调用objc_autoreleasePoolPop()
。ui
通过整理,能够把main函数中实际的代码应该是相似这样的。this
int main(int argc, const char * argv[]) { { void * atautoreleasepoolobj = objc_autoreleasePoolPush(); // do whatever you want objc_autoreleasePoolPop(atautoreleasepoolobj); } return 0; }
下面就是正式的经过源码来学习Autoreleasepool了spa
上面咱们提到了mian函数中的@autoreleasepool
其实最终转成了objc_autoreleasePoolPush()
和objc_autoreleasePoolPop()
这两个方法的调用,咱们去源码中搜一下这两个函数。
void * objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); }
void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); }
有源码能够看出,这两个函数就是对AutoreleasePoolPage类的pish和pop方法的封装,因此咱们来着重看AutoreleasePoolPage类。
简化一下代码,先看类的部分属性
class 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 magic_t const magic; id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; };
熟悉链表的朋友,看到这个parent和child就差很少能猜出来了,每个自动释放池实际上是一个双向链表,链表的每个结点就是这个AutoreleasePoolPage,每一个AutoreleasePoolPage的大小为4096字节
#define I386_PGBYTES 4096 #define PAGE_SIZE I386_PGBYTES
若是咱们的一个AutoreleasePoolPage
被初始化在内存的 0x100816000 ~ 0x100817000 中,它在内存中的结构以下:
其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的 0x100816038 ~ 0x100817000 都是用来存储加入到自动释放池中的对象。
begin()
和 end()
这两个实例方法帮助咱们快速获取 0x100816038 ~ 0x100817000 这一范围的边界地址。
next
指向了下一个为空的内存地址,若是next
指向的地址加入一个 object
,它就会以下图所示移动到下一个为空的内存地址中:
从图片中咱们能够看到在AutoreleasePoolPage的栈中出现了一个POOL_SENTINEL
,咱们称之为哨兵对象。
#define POOL_SENTINEL nil
其实哨兵对象只是nil的别名,他有啥做用呢 ?
每一个自动释放池初始化在调用objc_autoreleasePoolPush
的时候,都会把一个POOL_SENTINEL
push到自动释放池的栈顶,而且返回这个POOL_SENTINEL
的地址。
int main(int argc, const char * argv[]) { { void * atautoreleasepoolobj = objc_autoreleasePoolPush(); // do whatever you want objc_autoreleasePoolPop(atautoreleasepoolobj); } return 0; }
上面这个atautoreleasepoolobj就是一个POOL_SENTINEL
。
能够看到在调用objc_autoreleasePoolPop时,会传进去这个地址:
由于自动释放池是一个双向链表,并且每个page的空间有限,因此会存在当前page已满的状况,也就出现了一个自动释放池跨越几个page的状况,因此在release的时候,也要顺着链表所有清理掉。
static inline void *push() { id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }
经查阅,DebugPoolAllocation是来区别调试模式的,咱们主要看autoreleaseFast这个函数。
static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } }
hotPage( )咱们能够理解为获取当前的AutoreleasePoolPage,获取到当前page以后又根据page是否已满来区别处理。
有 hotPage 而且当前 page 不满
有 hotPage 而且当前 page 已满
无 hotPage
static __attribute__((noinline)) 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 { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); }
上面代码中的函数是在page已满的时候调用,从源码中能够看出经过传入的page遍历链表,直到找到一个未满的page,若是遍历到最后一个结点也没有未满的,就新建一个new AutoreleasePoolPage(page);
。而且要把找到的知足条件的这个page设置为hotPage。
咱们看一下pop的源码,里面内容不少,咱们精简了一下。
static inline void pop(void *token) { AutoreleasePoolPage *page = pageForPointer(token); id *stop = (id *)token; page->releaseUntil(stop); if (page->child) { // 不清楚为何要用下面这个if分类 if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } }
经过token调用pageForPointer()
方法获取到当前的AutoreleasePoolPage,而后调用releaseUntil()
释放page中的对象,直到stop,child节点调用kill()
方法。
void kill() { AutoreleasePoolPage *page = this; //经过循环先找到最后一个节点 while (page->child) page = page->child; AutoreleasePoolPage *deathptr; //经过do-while循环,依次从后往前置为nil do { deathptr = page; page = page->parent; if (page) { page->unprotect(); page->child = nil; page->protect(); } delete deathptr; } while (deathptr != this); }
pageForPointer()
主要是经过内存地址的操做,获取当前指针所在页的首地址, releaseUntil()
也是经过一个循环来释放全部的对象,具体的源码你们能够本身看一下。
在没有手动加AutoreleasePool的状况下,Autorelease对象都是在当前的runloop迭代结束时释放的,由于系统在每一个runloop迭代中都加入了自动释放池Push和Pop。
这个问题又要跟runloop联系到一块儿了,等咱们研究过runloop的源码,对这个问题应该就有更深入的认识了。