美文网首页
剖析AutoReleasePool

剖析AutoReleasePool

作者: 侯志桐 | 来源:发表于2020-10-28 16:39 被阅读0次

    本文所引用的资料如下

    objc4-680源码
    sunnyxx的黑幕背后的Autorelease
    Clang官方文档

    我是前言

    Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[obj autorelease]来延迟内存的释放是一件简单自然的事,ARC下,我们甚至可以完全不知道Autorelease就能管理好内存。而在这背后,objc和编译器都帮我们做了哪些事呢,它们是如何协作来正确管理内存的呢?刨根问底,一起来探究下黑幕背后的Autorelease机制。

    AutoReleasePool数据结构

    谈释放一定得说它本身的数据结构以及实现方法才行.先看AutoReleasePool的数据结构.首先它是一个C++的对象.单个对象大小限制是4096字节(PAGE_MAX_SIZE->PAGE_SIZE->I386_PGBYTES -> 4096).本身是一个双向链表.主要属性是parent(父链表),child(子链表),thread(初始化时传入pthread_self(),当前线程.AutoReleasePool是与线程相关的).next(最后一个加入AutoReleasePool的对象位于AutoReleasePool对象的偏移量)

    >>//NSObject.mm Line:581
    class AutoreleasePoolPage 
    {
    
    #define POOL_SENTINEL nil
        static pthread_key_t const key = AUTORELEASE_POOL_KEY;
        static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
        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
        static size_t const COUNT = SIZE / sizeof(id);
    
        magic_t const magic;
        id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    

    Autorelease对象什么时候释放?

    这个问题拿来做面试题,问过很多人,没有几个能答对的。很多答案都是“当前作用域大括号结束时释放”,显然木有正确理解Autorelease机制。
    在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

    • RunLoop自动释放

    在讲述执行Push和Pop时机的时候又需要说一下RunLoop相关的东西了.传送门
    简述:App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
    第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
    在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

    • RunLoop手动释放

    手动释放指的是在ARC环境下使用@autoreleasepool代码块包裹的autoRelease对象

    @autoreleasepool {
        //void *context = objc_autoreleasePoolPush()
        Person *person = [[Person alloc] copyObject];
        //内部返回前调用了autorelease方法
        [person print1];
         //objc_autoreleasePoolPop(context)
      }
    
    • NSObject->autoRelease方法源码如下

    简述流程

    • 调用对象的autorelease方法
    • 调用内部的rootAutorelease方法.
    • 调用内部的rootAutorelease2方法
    • 调用AutoreleasePoolPage::autorelease方法.传入self
    • 调用autoreleaseFast方法,传入参数
    • 在autoreleaseFast中通过hotPage方法获得当前使用的AutoreleasePoolPage对象.如果存在并且未满,则调用page的add方法将对象加入AutoreleasePoolPage的栈中.并移动next到下一位置.否为根据page对象是否存在执行不同的方法

    如果page存在.则表示full返回true.则执行如下代码.循环遍历child属性到最后一页.然后创建一个新AutoreleasePoolPage对象.并将最后一个page对象设置为新创建对象的parent.然后调用setHotPage函数设置为当前page.并调用page->add方法将对象加入到page的栈中.否则走autoreleaseNoPage方法初始化page

    //NSObject.mm Line:839
    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);
       }
    }
    //NSObject.mm Line:851
    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);
       } while (page->full());
    
       setHotPage(page);
       return page->add(obj);
    }
    //NSObject.mm Line:869
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
       // No pool in place.
       assert(!hotPage());
    
       if (obj != POOL_SENTINEL  &&  DebugMissingPools) {
           // We are pushing an object with no pool in place,
           // and no-pool debugging was requested by environment.
           _objc_inform("MISSING POOLS: Object %p of class %s "
                        "autoreleased with no pool in place - "
                        "just leaking - break on "
                        "objc_autoreleaseNoPool() to debug",
                        (void*)obj, object_getClassName(obj));
           objc_autoreleaseNoPool(obj);
           return nil;
       }
    
       // Install the first page.
       AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
       setHotPage(page);
    
       // Push an autorelease pool boundary if it wasn't already requested.
       if (obj != POOL_SENTINEL) {
           page->add(POOL_SENTINEL);
       }
    
       // Push the requested object.
       return page->add(obj);
    }
    
    • AutoRelease相关代码如下

    //NSObject.mm Line:2181
    - (id)autorelease {
        return ((id)self)->rootAutorelease();
    }
    //NSObject.mm Line:596
    inline id  objc_object::rootAutorelease()
    {
        assert(!UseGC);
    
        if (isTaggedPointer()) return (id)this;
        if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
    
        return rootAutorelease2();
    }
    //NSObject.mm Line:1093
    __attribute__((noinline,used))
    id  objc_object::rootAutorelease2()
    {
        assert(!isTaggedPointer());
        return AutoreleasePoolPage::autorelease((id)this);
    }
    //NSObject.mm Line:910
    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  *dest == obj);
        return obj;
    }
    //NSObject.mm Line:839
    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);
        }
    }
    //NSObject.mm Line:812
    static inline AutoreleasePoolPage *hotPage()
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if (result) result->fastcheck();
        return result;
    }
    
    
    • Push以及Pop源码如下

    //NSObject.mm Line:1764
    void *_objc_autoreleasePoolPush(void)
    {
        return objc_autoreleasePoolPush();
    }
    //NSObject.mm Line:1749
    void *objc_autoreleasePoolPush(void)
    {
        if (UseGC) return nil;
        return AutoreleasePoolPage::push();
    }
    //NSObject.mm Line:920
     static inline void *push() 
    {
      id *dest;
      if (DebugPoolAllocation) {
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_SENTINEL);
      } else {
        dest = autoreleaseFast(POOL_SENTINEL);
      }
      assert(*dest == POOL_SENTINEL);
       return dest;
     }
    
    //NSObject.mm Line:1770
    void _objc_autoreleasePoolPop(void *ctxt)
    {
        objc_autoreleasePoolPop(ctxt);
    }
    //NSObject.mm Line:1756
    void objc_autoreleasePoolPop(void *ctxt)
    {
        if (UseGC) return;
        AutoreleasePoolPage::pop(ctxt);
    }
    //NSObject.mm Line:933
    static inline void pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
    
        page = pageForPointer(token);
        stop = (id *)token;
        if (DebugPoolAllocation  &&  *stop != POOL_SENTINEL) {
            // This check is not valid with DebugPoolAllocation off
            // after an autorelease with a pool page but no pool in place.
            _objc_fatal("invalid or prematurely-freed autorelease pool %p; ",
                        token);
        }
    
        if (PrintPoolHiwat) printHiwat();
    
        page->releaseUntil(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();
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:剖析AutoReleasePool

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