AutoreleasePool的那些事

AutoreleasePool的那些事

我们都知道一个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();
}

void
objc_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

void
objc_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.(引用iOS Objective-C底层 part3:live^ARC )

最后琐事

  • 使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 这里被一个局部@autoreleasepool包围着
}];
复制代码

这段引自sunnyxx大神的文章

不过不知道是不是我理解的问题,我在代码中没有看到block中有autoreleasepool
main写了如下代码

编译成汇编后,并没有看到
autoreleasepool的身影

然后在
AutoreleasePoolPage::push()打上断点

可以看到
enumerateObjectsWithOptions:usingBlock:这个方法中是有
push
pop操作的(不知道理解的对不对,如果不对,请轻喷)

存疑:
其实AutoreleasePool还有很多可以说的,比如AutoreleasePool是在什么时候释放的,在下功力浅薄只知道在runloop每次循环的开始时候会去push,结束的时候去pop但是真的深入就不了解了,此处暂且存疑,待日后修炼归来再来解答


文章参考:
黑幕背后的Autorelease
iOS Objective-C底层 part3:live^ARC
自动释放池的前世今生

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/101608.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • ZigBee协议栈Zstack介绍

    ZigBee协议栈Zstack介绍关于ZigBee协议栈的各个层的一些介绍

  • java json decode 中文_PHP实现json_decode不转义中文的方法[通俗易懂]

    java json decode 中文_PHP实现json_decode不转义中文的方法[通俗易懂]本文实例讲述了PHP实现json_decode不转义中文的方法。分享给大家供大家参考,具体如下:默认情况下PHP的json_decode方法会把特殊字符进行转义,还会把中文转为Unicode编码形式。这使得数据库查看文本变得很麻烦。所以我们需要限制对于中文的转义。对于PHP5.4+版本,json_decode函数第二个参数,可以用来限制转义范围。要限制中文,使用JSON_UNESCAPED_U…

  • linux的netstat命令_iostat命令详解

    linux的netstat命令_iostat命令详解netstat可以用来查Linx网络系统接口的状态信息,统计信息,打开的socket连接,以及路由表等等。

  • java常用的io流_io流java

    java常用的io流_io流javaIO流大家肯定不陌生,简单整理了一下常用IO流基本用法,其他的IO流以后有时间在整理。1.基本概念IO:Java对数据的操作是通过流的方式,IO流用来处理设备之间的数据传输,上传文件和下载文件,Java用于操作流的对象都在IO包中。2.IO流的分类图示:(主要IO流)3.字节流(1).字节流基类1).InputStreamInputStream:字节输入流基类,抽象类是表示字节输入流的所有

    2022年10月20日
  • jmeter吞吐量和并发数关系_java获取cpu使用率

    jmeter吞吐量和并发数关系_java获取cpu使用率如何计算进程调度算法的吞吐量(Howtocalculatethroughputofaprocessschedulingalgorithm)我正在尝试使用Java计算FCFS算法的吞吐量,但它总是给我零。我做得对吗?startTime=System.nanoTime();total=FCFC(copyBurstTime,copyArrivalTime);estimated…

    2022年10月21日
  • 【Python】解决Pycharm中pip更新问题「建议收藏」

    【Python】解决Pycharm中pip更新问题「建议收藏」pycharm安装第三方模块失败,要求更新pip

    2022年10月25日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号