博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AutoreleasePool的那些事
阅读量:6881 次
发布时间:2019-06-27

本文共 12958 字,大约阅读时间需要 43 分钟。

我们都知道一个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 删除。具体为什么要又区分不是特别理解...

到此pushpop就已经说完了。在我们的理解中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但是真的深入就不了解了,此处暂且存疑,待日后修炼归来再来解答


文章参考:

转载地址:http://cybbl.baihongyu.com/

你可能感兴趣的文章
把图片保存到数据库里
查看>>
【CUDA学习】全局存储器
查看>>
Reward HDU
查看>>
ISSkin 使用技巧,WinXP 下的窗口阴影
查看>>
HttpClient传递Cookie
查看>>
网站可用性测试及优化指南-随笔2
查看>>
Hammer.js
查看>>
WebService学习总结(四)——调用第三方提供的webService服务
查看>>
Selenium学习笔记之外部化相关测试数据---xml
查看>>
基于HTML5 Canvas实现的图片马赛克模糊特效
查看>>
原: 安装VMtools过程流水帐
查看>>
NYOJ1026 阶乘末尾非0 【模板】
查看>>
设计模式入门之装饰器模式Decorator
查看>>
aSmack连接server异常smack.SmackException$ ConnectionException thrown by XMPPConnection.connect();...
查看>>
System.Data.SqlClient.SqlException: 数据类型 text 和 varchar 在 equal to 运算符中不兼容。...
查看>>
GIT入门笔记(20)- git 开发提交代码过程梳理
查看>>
【Vue 入门】使用 Vue2 开发一个展示项目列表的应用
查看>>
容器使用笔记(List篇)
查看>>
Duilib Edit编辑框禁止输入中文的方法
查看>>
vim使用技巧大全
查看>>