美文网首页
autoreleasepool

autoreleasepool

作者: forping | 来源:发表于2021-03-08 15:53 被阅读0次

    main 函数可以说是在整个 iOS 开发中非常不起眼的一个函数,却是整个 iOS 应用的入口。

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    @autoreleasepool 结构

    通过对下面代码反汇编,

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
        }
        return 0;
    }
    
      FPRuntimeTest`main:
        0x100003f10 <+0>:  pushq  %rbp
        0x100003f11 <+1>:  movq   %rsp, %rbp
        0x100003f14 <+4>:  subq   $0x10, %rsp
        0x100003f18 <+8>:  movl   $0x0, -0x8(%rbp)
        0x100003f1f <+15>: movl   %edi, -0x4(%rbp)
        0x100003f22 <+18>: movq   %rsi, -0x10(%rbp)
        0x100003f26 <+22>: callq  0x100003f48               ; symbol stub for: objc_autoreleasePoolPush
    ->  0x100003f2b <+27>: movq   %rax, %rdi
        0x100003f2e <+30>: callq  0x100003f42               ; symbol stub for: objc_autoreleasePoolPop
        0x100003f33 <+35>: xorl   %eax, %eax
        0x100003f35 <+37>: addq   $0x10, %rsp
        0x100003f39 <+41>: popq   %rbp
        0x100003f3a <+42>: retq   
    

    可以知道,调用了objc_autoreleasePoolPushobjc_autoreleasePoolPop函数.
    在runtime源码中查找

    void *objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    void objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }
    

    调用了 AutoreleasePoolPage 类 的 push()pop()方法

    class AutoreleasePoolPage : private AutoreleasePoolPageData
    {
        friend struct thread_data_t; // 声明为友元
    
    public:
        static size_t const SIZE =
    #if PROTECT_AUTORELEASEPOOL
            PAGE_MAX_SIZE;  // must be multiple of vm page size
    #else
            PAGE_MIN_SIZE;  // size and alignment, power of 2 // 1左移12位=4096一个自动释放池页的大小,也是一个页大小
    #endif
        
    private:
        static pthread_key_t const key = AUTORELEASE_POOL_KEY; // 用来从线程中取出来当前释放池的key
        static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
        static size_t const COUNT = SIZE / sizeof(id);// 存储对象的数量
        static size_t const MAX_FAULTS = 2;
    
    /*
    当仅推入一个池且从未包含任何对象时,EMPTY_POOL_PLACEHOLDER就会存储在TLS中。 
    当顶层(即libdispatch)推送并弹出池但从不使用它们时,这样可以节省内存。
    */
    #   define EMPTY_POOL_PLACEHOLDER ((id*)1)
    // 哨兵对象,nil
    #   define POOL_BOUNDARY nil
    ···
    }
    

    父类

    struct AutoreleasePoolPageData
    {
    #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
        struct AutoreleasePoolEntry {
            uintptr_t ptr: 48;
            uintptr_t count: 16;
    
            static const uintptr_t maxCount = 65535; // 2^16 - 1
        };
        static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
    #endif
    
        magic_t const magic;
        __unsafe_unretained id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    
        AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
            : magic(), next(_next), thread(_thread),
              parent(_parent), child(nil),
              depth(_depth), hiwat(_hiwat)
        {
        }
    };
    

    由上可得,下面的代码可以理解为AutoreleasePoolPage的结构

    class AutoreleasePoolPage {
        magic_t const magic;
        id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    };
    
    • magic 用于对当前 AutoreleasePoolPage 完整性的校验
    • thread 保存了当前页所在的线程

    parent 和 child 就是用来构造双向链表的指针。

    每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)

    magic_t
    struct magic_t {
        static const uint32_t M0 = 0xA1A1A1A1;
    #   define M1 "AUTORELEASE!"
        static const size_t M1_len = 12;
        uint32_t m[4];
    
        magic_t() {
            assert(M1_len == strlen(M1));//判断M1是否是12个字符长度
            assert(M1_len == 3 * sizeof(m[1]));//判断M1_len是不是12
    
            m[0] = M0;//将0xA1A1A1A1赋值给M0
            //把M1字符串地址的12个字节复制到m1地址所指向的空间中
            strncpy((char *)&m[1], M1, M1_len);
        }
         //析构函数
        ~magic_t() {
            m[0] = m[1] = m[2] = m[3] = 0;
        }
         //检测函数
        bool check() const {
            return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
        }
         //快速检测函数
        bool fastcheck() const {
    #if CHECK_AUTORELEASEPOOL
            return check();
    #else
            return (m[0] == M0);
    #endif
        }
    
    #   undef M1
    };
    

    push

        static inline void *push() 
        {
            id *dest;
    // 判断是不是在Debug模式下,在 Debug 情况下每一个自动释放池都以一个新的poolPage开始会去调用autoreleaseNewPage方法,否则的话就去调用autoreleaseFast函数
            if (slowpath(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;
        }
    

    autoreleaseFast

     static inline id *autoreleaseFast(id obj)
        {
            AutoreleasePoolPage *page = hotPage();
            if (page && !page->full()) { // 有page且page没有存满
                return page->add(obj);
            } else if (page) {
                return autoreleaseFullPage(obj, page);// 有page,但是存满了
            } else {
                return autoreleaseNoPage(obj); // 没有page
            }
        }
    

    上述方法分三种情况选择不同的代码执行:

    • 有 hotPage 并且当前 page 不满
      -- 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
    • 有 hotPage 并且当前 page 已满
      -- 调用 autoreleaseFullPage 初始化一个新的页
      -- 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
    • 无 hotPage
      -- 调用 autoreleaseNoPage 创建一个 hotPage
      -- 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中

    最后的都会调用 page->add(obj) 将对象添加到自动释放池中。

    hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。

    hotPage()

    取出当前的page,如果取出的page是EMPTY_POOL_PLACEHOLDER,那就说明是空的池子,就说明还没有page,直接返回nil
    EMPTY_POOL_PLACEHOLDER其实就是当一个释放池没有包含任何对象,又被推入栈中,就存储在TLS(Thread_local_storage)中,叫做空池占位符

    我们会发现里面有一个tls_get_direct()函数,其实就是通过这个函数去取出当前的page的,其中tls的意思就是线程局部存储(Thread Local Storage)的缩写。

     static inline AutoreleasePoolPage *hotPage() 
        {
            AutoreleasePoolPage *result = (AutoreleasePoolPage *)
                tls_get_direct(key);
            if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
            if (result) result->fastcheck();
            return result;
        }
    
        id *add(id obj)
        {
            ASSERT(!full());
            unprotect();//解除保护
            id *ret;
            ret = next;  // faster than `return next-1` because of aliasing
            *next++ = obj;//将obj插入到page中并且重新去设置next的指向
            protect();//设置保护
            return ret;
        }
    

    我们主要去看unprotect()方法和protect()方法
    这个函数在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性,函数原型int mprotect(const void *start, size_t len, int prot); 这个函数的区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。关于这一点在AutoreleasePoolPage类当中也有提及

    autoreleaseFullPage

     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());//判断是不是最新的page
            ASSERT(page->full()  ||  DebugPoolAllocation);////判断page是不是满了或者是不是Debug模式下
    
            do {
    //判断page->child是不是存在,如果存在的话就让page = page->child
                if (page->child) page = page->child;
    //否则就去创建
                else page = new AutoreleasePoolPage(page);
            } while (page->full());
    
            setHotPage(page);
            return page->add(obj);
        }
    

    setHotPage

        static inline void setHotPage(AutoreleasePoolPage *page) 
        {
            if (page) page->fastcheck();
            tls_set_direct(key, (void *)page);
        }
    

    autoreleaseNoPage

        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()); //判断hotPage()返回的page是否为空,如果为空的话才能继续下去
    
            bool pushExtraBoundary = false; // 是否要push边界对象
    //如果有占位的EMPTY_POOL_PLACEHOLDER,就去设置需要去push一个边界对象
            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.
                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", 
                             objc_thread_self(), (void*)obj, object_getClassName(obj));
                objc_autoreleaseNoPool(obj);
                return nil;
            }
    //如果不是debug模式以及obj是边界对象就去设置空的占位池子也就是   EMPTY_POOL_PLACEHOLDER
            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.
                return setEmptyPoolPlaceholder();
            }
    
            // We are pushing an object or a non-placeholder'd pool.
    
            // Install the first page.
    // Install the first page. 创建第一个page
            AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    //设置最新的page
            setHotPage(page);
            
            // Push a boundary on behalf of the previously-placeholder'd pool.
            if (pushExtraBoundary) {
                /*插入一个边界对象会走这里的方法肯定是之前已经设置过   setEmptyPoolPlaceholder也就是说不是在Debug模式下,并且obj不是POOL_BOUNDARY*/
    
                page->add(POOL_BOUNDARY);
            }
            
            // Push the requested object or pool.
          // 添加一个对象
            return page->add(obj);
        }
    

    pop

    void
    objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }
    

    pop函数里面的参数是调用objc_autoreleasePoolPush()的返回值,返回值是EMPTY_POOL_PLACEHOLDER或一个地址, 地址存储的值是POOL_BOUNDARY

    static inline void
        pop(void *token)
        {
            AutoreleasePoolPage *page;
            id *stop;
            //判断token是否是EMPTY_POOL_PLACEHOLDER 
            if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
                // Popping the top-level placeholder pool.
                //调用hotPage()方法判断当前的页存不存在
                page = hotPage();
                if (!page) {
                    // Pool was never used. Clear the placeholder.
                    return setHotPage(nil);
                }
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
    //先调用coldPage()寻找到最上面的祖宗结点,然后调用begin方法,返回的即可以用于存放对象地址的第一个位置,然后进行pop
                page = coldPage();
                token = page->begin();
            } else {
               //根据token寻找到页所在的地址
                page = pageForPointer(token);
            }
    
            stop = (id *)token;
            if (*stop != POOL_BOUNDARY) { // 如果token存储的不是nil
                if (stop == page->begin()  &&  !page->parent) { // 传进来的是  EMPTY_POOL_PLACEHOLDER
    //最冷页面的开始可能不是POOL_BOUNDARY:
    // 1.弹出顶级池,将冷页保留在原处
    // 2.一个没有池的对象被自动释放
                } else {
                    // Error. For bincompat purposes this is not 
                    // fatal in executables built with old SDKs.
                    return badPop(token); 
                }
            }
    
            if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
                return popPageDebug(token, page, stop);
            }
    
            return popPage<false>(token, page, stop);
        }
    

    pageForPointer 找到token地址对应的autorelease页

    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
        {
            AutoreleasePoolPage *result;
            uintptr_t offset = p % SIZE;
    
            ASSERT(offset >= sizeof(AutoreleasePoolPage));
    
            result = (AutoreleasePoolPage *)(p - offset);
            result->fastcheck();
    
            return result;
        }
    

    popPage

     template<bool allowDebug>
        static void
        popPage(void *token, AutoreleasePoolPage *page, id *stop)
        {
            if (allowDebug && PrintPoolHiwat) printHiwat();
    
    //释放直到遇到stop
            page->releaseUntil(stop);
    
            // memory: delete empty children
            if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
                // special case: delete everything during page-per-pool debugging
                AutoreleasePoolPage *parent = page->parent;
                page->kill();
                setHotPage(parent);
            } else if (allowDebug && 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();
                }
            }
        }
    

    释放直到遇到stop

        void releaseUntil(id *stop) 
        {
            // Not recursive: we don't want to blow out the stack 
            // if a thread accumulates a stupendous amount of garbage
            
            while (this->next != stop) { // while循环
                // Restart from hotPage() every time, in case -release 
                // autoreleased more objects
                AutoreleasePoolPage *page = hotPage(); // 当前页
    
                // fixme I think this `while` can be `if`, but I can't prove it
                while (page->empty()) { // 如果页变空了,设置父页为当前页
                    page = page->parent;
                    setHotPage(page);
                }
    
                page->unprotect(); // 解除保护
    #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;
    
                // create an obj with the zeroed out top byte and release that
                id obj = (id)entry->ptr;
                int count = (int)entry->count;  // grab these before memset
    #else
                id obj = *--page->next; // 获取需要释放的对象
    #endif
                memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 空间存储的值变成0xA3A3A3A3
                page->protect();
    
                if (obj != POOL_BOUNDARY) {
    #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                    // release count+1 times since it is count of the additional
                    // autoreleases beyond the first one
                    for (int i = 0; i < count + 1; i++) {
                        objc_release(obj);
                    }
    #else
                    objc_release(obj); // 引用计数-1
    #endif
                }
            }
    
            setHotPage(this); // 设置当前页
    
    #if DEBUG
            // we expect any children to be completely empty
            for (AutoreleasePoolPage *page = child; page; page = page->child) {
                ASSERT(page->empty());
            }
    #endif
        }
    

    kill

        void kill() 
        {
            // Not recursive: we don't want to blow out the stack 
            // if a thread accumulates a stupendous amount of garbage
            AutoreleasePoolPage *page = this;
            while (page->child) page = page->child; // 找到最后一页
    
            AutoreleasePoolPage *deathptr;
            do {
                deathptr = page;
                page = page->parent;
                if (page) {
                    page->unprotect();
                    page->child = nil;
                    page->protect();
                }
                delete deathptr;
            } while (deathptr != this); // 通过 parent 进行autorelease的释放
        }
    

    autorelease

    static inline id autorelease(id obj)
        {
            ASSERT(!obj->isTaggedPointerOrNil());
            id *dest __unused = autoreleaseFast(obj);
    #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
    #else
            ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    #endif
            return obj;
        }
    

    相关文章

      网友评论

          本文标题:autoreleasepool

          本文链接:https://www.haomeiwen.com/subject/okdpqltx.html