我们都知道一个iOS应用的如果是在main
函数中,它的实现是
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}复制代码
我们看到在main
中有个@autoreleasepool
,那它到底是什么呢?让我们转成.cpp
看下:
xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m 复制代码
转换成c++
后是
int main(int argc, char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")))); }}复制代码
@autoreleasepool
对应的就是个__AtAutoreleasePool
struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj;};复制代码
因为是C++
代码,所以可以看出这个结构体构造函数会调用objc_autoreleasePoolPush()
,析构函数会调用objc_autoreleasePoolPop();
,所以main
函数可以理解为:
int main(int argc, char * argv[]) { { atautoreleasepoolobj = objc_autoreleasePoolPush(); ... objc_autoreleasePoolPop(atautoreleasepoolobj); }}复制代码
那么objc_autoreleasePoolPush()
与objc_autoreleasePoolPop()
都做了什么?我们可以从源码中一窥究竟
void *objc_autoreleasePoolPush(void){ return AutoreleasePoolPage::push();}voidobjc_autoreleasePoolPop(void *ctxt){ AutoreleasePoolPage::pop(ctxt);}复制代码
可以看到这两个方法只是调用了AutoreleasePoolPage
的静态方法,那么AutoreleasePoolPage
是什么?下面我们就去看一看
AutoreleasePoolPage
AutoreleasePoolPage
是一个C++
的类,并且是一个双向链表
class AutoreleasePoolPage { magic_t const magic;//16 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
的完整性,thread
保存了当前所在线程
没一个自动释放池都是由多个AutoreleasePoolPage
组成的,而每个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 */复制代码
可以看出每个AutoreleasePoolPage
的大小都是4096
也就是16进制0x1000
,而其中AutoreleasePoolPage
自己的成员占56位
,剩下的空间用于存储加入自动释放池的对象,AutoreleasePoolPage
提供了两个方法begin()
与end()
可以方便快速找到存储自动释放池对象的范围
id * begin() { return (id *) ((uint8_t *)this+sizeof(*this));//当前`AutoreleasePoolPage`指针 + `AutoreleasePoolPage`大小得到起始位置 } id * end() { return (id *) ((uint8_t *)this+SIZE);//当前`AutoreleasePoolPage`指针 + 整个`AutoreleasePoolPage`大小(4096)得到结束位置 }复制代码
next
指针则指向了下一个为空的位置
大致的AutoreleasePoolPage
我们已经了解了,那么我们回头去看下push
操作
push
void *objc_autoreleasePoolPush(void){ return AutoreleasePoolPage::push();}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; }复制代码
首先,我们看到传入了个POOL_BOUNDARY
宏,这个宏只是个nil
# define POOL_BOUNDARY nil复制代码
从后面我们可以看到,传进去这个nil
后会被加入自动释放池,并将这个值返回回来,然后在后面使用pop
操作的时候传入这个POOL_BOUNDARY
时,会一直release
自动释放池中的对象直到找到第一个POOL_BOUNDARY
int main(int argc, char * argv[]) { { atautoreleasepoolobj = objc_autoreleasePoolPush(); ... objc_autoreleasePoolPop(atautoreleasepoolobj); }}复制代码
后面讲到
然后可以看到调用pop
时候我们在细说具体是怎么释放的autoreleaseFast
函数(DebugPoolAllocation
我们不管)
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
,然后剩下要分三种情况
- 当前有
hotpage
并且这个hotpage
并没有满,调用add()
函数
if (page && !page->full()) { return page->add(obj); }复制代码
会调用add()
方法
id *add(id obj){ assert(!full()); unprotect(); id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; /* *next++ = obj;看着可能会有点抽象了,我们给展开看 *next = obj; next++; */ protect(); return ret; }复制代码
这个方法很简单,将当前传入的对象加入第一个为空的位置(next)指向的位置,然后把next
指针向后挪一位,最后返回传入的这个对象
- 自动释放池有
hotpage
,但是hotpage
已经满了,会调用autoreleaseFullPage()
函数
if (page) { return autoreleaseFullPage(obj, page);}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);//创建一个新的page 将上一个的page child指针指向 新的page } while (page->full()); setHotPage(page); return page->add(obj); }复制代码
这个方法里面在遍历整个AutoreleasePoolPage
链表,找到不满的那个page
或者如果遍历到最后一个page
也都满了就创建一个新的page
,并将这个page
设置为hotPage
,最后调用add()
方法
- 自动释放池没有
hotPage
,会调用autoreleaseNoPage()
函数
else { return autoreleaseNoPage(obj);} static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet assert(!hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { // We are pushing a second pool over the empty placeholder pool // or pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. //1,判断是否有空page pushExtraBoundary = true; } else if (obj != POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", pthread_self(), (void*)obj, object_getClassName(obj)); //Debug环境 忽略 objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. // 2,如果传进来的是`POOL_BOUNDARY`则设置一个空page 使用tls技术 以键值对的方式存储 return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder'd pool. // Install the first page. //2,创建自动释放池中第一个page AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); //3,将这个page设置为hotPage setHotPage(page); // Push a boundary on behalf of the previously-placeholder'd pool. if (pushExtraBoundary) { //4,传入哨兵对象(POOL_BOUNDARY) page->add(POOL_BOUNDARY); } // Push the requested object or pool. //5,添加对象进自动释放池 return page->add(obj); }复制代码
1,判断是否有空page
2,如果传进来的是POOL_BOUNDARY
则设置一个空page 使用tls技术 以键值对的方式存储 3,将这个page设置为hotPage 4,传入哨兵对象(POOL_BOUNDARY) 5,添加对象进自动释放池 push
操作就是这样的,下面我们继续看下pop
pop
voidobjc_autoreleasePoolPop(void *ctxt){ AutoreleasePoolPage::pop(ctxt);}复制代码
这个地方传入的ctxt
正式调用push
时返回的那个哨兵对象POOL_BOUNDARY
(上文有说到)
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; //这块貌似关于tls 能力有限不是太懂就不丢人现眼了,如果有大神对这些比较了解 望不吝赐教 if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. if (hotPage()) { // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. pop(coldPage()->begin()); } else { // Pool was never used. Clear the placeholder. setHotPage(nil); } return; } page = pageForPointer(token);//根据token(一个指针)获取当前的page stop = (id *)token; if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold page in place // 2. an object is autoreleased with no pool } else { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. return badPop(token); } } if (PrintPoolHiwat) printHiwat(); page->releaseUntil(stop);//释放栈中对象 直到stop (stop正常情况应该是) // 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(); } } }复制代码
上面貌似关于tls 能力有限不是太懂就不丢人现眼了,如果有大神对这些比较了解 望不吝赐教
这个方法的下半部分主要是以传入的token
为标记从上往下一直进行release
操作,指导遇到token
为止,最后判断当前 page 使用不满一半,从 child page 开始将后面所有 page 删除;当前 page 使用超过一半,从 child page 的 child page(即孙子,如果有的话)开始将后面所有的 page 删除。具体为什么要又区分不是特别理解...
到此push
与pop
就已经说完了。在我们的理解中ARC环境下编译器会自动的给我们在变量后面加上retain
,release
,autorelease
等方法,下面我们就去看下autorelease
的实现
autorelease
- (id)autorelease { return ((id)self)->rootAutorelease();}objc_object::rootAutorelease(){ if (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; return rootAutorelease2();}inline id objc_object::rootAutorelease(){ if (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; return rootAutorelease2();}__attribute__((noinline,used))id objc_object::rootAutorelease2(){ assert(!isTaggedPointer()); return AutoreleasePoolPage::autorelease((id)this);}static inline id autorelease(id obj) { assert(obj); assert(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; }复制代码
别的先不管,我们可以看到到方法的最下面还是调用到了autoreleaseFast()
方法,这样就和上面的push
操作类似了。
TLS (Thread Local Storage)
那么事实上编译器真的只是在我们代码的后面加上了autorelease
吗?我们写份代码
Hopper Disassemebler
中进行反编译看下 发现编译器并没有给我们添加 autorelease
,而是多了两个 objc_autoreleaseReturnValue
与 objc_retainAutoreleasedReturnValue
方法,我们一个个先看看 id objc_autoreleaseReturnValue(id obj){ if (prepareOptimizedReturn(ReturnAtPlus1)) return obj; return objc_autorelease(obj);}static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition){ assert(getReturnDisposition() == ReturnAtPlus0); if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) { if (disposition) setReturnDisposition(disposition); return true; } return false;}复制代码
这个函数调用了prepareOptimizedReturn
,然后调用了callerAcceptsFastAutorelease
,传入一个__builtin_return_address(0)
__builtin_return_address
接收一个称为 level 的参数。这个参数定义希望获取返回地址的调用堆栈级别。例如,如果指定 level 为 0,那么就是请求当前函数的返回地址。如果指定 level 为 1,那么就是请求进行调用的函数的返回地址,依此类推
接下来来看 callerAcceptsFastAutorelease
这个函数(以arm64为例):
static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra){ // fd 03 1d aa mov fp, fp // arm64 instructions are well-aligned if (*(uint32_t *)ra == 0xaa1d03fd) { return true; } return false;}复制代码
它检查了主调方在返回值之后是否紧接着调用了objc_retainAutoreleasedReturnValue
,如果是,就知道了外部是ARC环境,反之就走没被优化的老逻辑。
objc_autoreleaseReturnValue
将对象放入tls(Thread Local Storage)
;而外部由objc_retainAutoreleasedReturnValue
将对象由tls
中取出,这样就不用走autoreleasepool
了,而由tls
代劳了,这样就节省了autoreleasepool
对对象的存储,清除开销了。 那也就是说ARC下只要调用方和被调方都用ARC编译时,所建立的对象都不加入
autoreleasepool
.更简单的说我们自己写的类,调用工厂方法生成对象都不会放 入autoreleasepool
.(引用)
最后琐事
- 使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { // 这里被一个局部@autoreleasepool包围着}];复制代码
这段引自
不过不知道是不是我理解的问题,我在代码中没有看到block中有autoreleasepool
main
写了如下代码 编译成汇编后,并没有看到 autoreleasepool
的身影 然后在 AutoreleasePoolPage::push()
打上断点 可以看到 enumerateObjectsWithOptions:usingBlock:
这个方法中是有 push
与 pop
操作的(不知道理解的对不对,如果不对,请轻喷) 存疑:
其实AutoreleasePool
还有很多可以说的,比如AutoreleasePool
是在什么时候释放的,在下功力浅薄只知道在runloop
每次循环的开始时候会去push
,结束的时候去pop
但是真的深入就不了解了,此处暂且存疑,待日后修炼归来再来解答
文章参考: