autoreleasepool底层探索

autoreleasepool底层探索

IOS小彩虹2021-07-13 9:54:3970A+A-

#自动释放池初探 修改main.m里的代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
    return 0;
}

我们通过clang命令来看一下main.m的c++实现:

 clang -rewrite-objc main.m -o main.cpp

image.png
打开.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:

image.png

所以我们可以得出结论,自动释放池在初始化的时候执行了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 ?

  1. 大量的临时变量;
  2. 某些非UI操作;
  3. 自己创建的辅助线程等等。

要进行自动释放,第一步肯定是要先存进去,这样才能进行释放,每一个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 0x1000bd5c0
objc[42414]: 506 releases pending.
objc[42414]: [0x103003000]  ................  PAGE  (hot) (cold)
objc[42414]: [0x103003038]  ################ POOL 0x103003038
objc[42414]: [0x103003040]       0x10061d340  NSObject
objc[42414]: [0x103003048]       0x100604c70  NSObject
objc[42414]: [0x103003050]       0x100606ba0  NSObject
objc[42414]: [0x103003058]       0x1006079b0  NSObject
objc[42414]: [0x103003060]       0x10061d150  NSObject
objc[42414]: [0x103003068]       0x100614f70  NSObject
objc[42414]: [0x103003070]       0x100615280  NSObject
objc[42414]: [0x103003078]       0x100611700  NSObject
objc[42414]: [0x103003080]       0x10060ff80  NSObject
objc[42414]: [0x103003088]       0x10060cc30  NSObject
objc[42414]: [0x103003090]       0x100609e30  NSObject
objc[42414]: [0x103003098]       0x100609fd0  NSObject
objc[42414]: [0x1030030a0]       0x100605a00  NSObject
objc[42414]: [0x1030030a8]       0x100607810  NSObject
...
objc[42439]: [0x103002fe0]       0x10061e840  NSObject
objc[42439]: [0x103002fe8]       0x10061e850  NSObject
objc[42439]: [0x103002ff0]       0x10061e860  NSObject
objc[42439]: [0x103002ff8]       0x10061e870  NSObject
objc[42439]: [0x101005000]  ................  PAGE  (hot) 
objc[42439]: [0x101005038]       0x10061e880  NSObject
objc[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
    }

总结一下:

  1. 没有page的时候,创建第一个page,并且add一个边界符;
  2. 开始正常add对象;
  3. 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);
    }

总结一下:

  1. 删除page里的每一个对象,然后将page指向parent并置为hot,进行递归
  2. 删除所有空的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,优先级最低,保证其释放池子发生在其他所有回调之后。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们