美文网首页
AutoreleasePool的原理和实现

AutoreleasePool的原理和实现

作者: IFeng_iOSer | 来源:发表于2021-09-29 14:32 被阅读0次

    底层原理

    在ARC中,看一下@autoreleasepool底层代码具体是什么。

    1.查看@autoreleasepool{ }编译成C++代码

    使用编译器clang编译main.m转化成main.cpp文件(在终端:clang -rewrite-objc main.m)

    编译之后的main.cpp的代码,把主要的代码拷贝出来如下

    extern "C" __declspec(dllimport)
        void * objc_autoreleasePoolPush(void);
    extern "C" __declspec(dllimport)
         void objc_autoreleasePoolPop(void *);
    struct __AtAutoreleasePool { 
            __AtAutoreleasePool()
             {
                atautoreleasepoolobj = objc_autoreleasePoolPush();
              }  
          ~__AtAutoreleasePool(){
              objc_autoreleasePoolPop(atautoreleasepoolobj);
            } 
         void * atautoreleasepoolobj;
      };
    

    可以从以上代码看出来@autoreleasepool其实是objc_autoreleasePoolPush 和 objc_autoreleasePoolPop这两个方法组成的。

    总之:@autoreleasepool是由objc_autoreleasePoolPush 和 objc_autoreleasePoolPop方法构成的一个结构体。

    2.查看objc_autoreleasePoolPush和objc_autoreleasePoolPop

    参照苹果开源代码找到objc_autoreleasePoolPush和objc_autoreleasePoolPop两个方法,两个方法在NSObject.mm中实现(苹果开源代码:https://opensource.apple.com/tarballs/objc4/)

    void *objc_autoreleasePoolPush(void){    
        if (UseGC) return nil;
        return AutoreleasePoolPage::push();
    }
    void objc_autoreleasePoolPop(void *ctxt){    
        if (UseGC) return;    // fixme rdar://9167170   
        if (!ctxt) return;  
        AutoreleasePoolPage::pop(ctxt);
    }
    

    从上面可以发现,C++类AutoreleasePoolPage才是实际的实现所在,找到AutoreleasePoolPage:

    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      // 通过查询  PAGE_MAX_SIZE =  4096
        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;                             //page的复杂度                   
        uint32_t hiwat;
        ……
    

    magic用来校验AutoreleasePoolPage的结构是否完整

    next指向下一个即将产生的autoreleased对象的存放位置(当next == begin()时,表示AutoreleasePoolPage为空;当next == end()时,表示AutoreleasePoolPage已满

    thread指向当前线程一个AutoreleasePoolPage只会对应一个线程,但一个线程可以对应多个AutoreleasePoolPage;

    parent指向父结点,第一个结点的 parent 值为 nil;

    child指向子结点,最后一个结点的 child 值为 nil;

    depth代表深度,第一个page的depth为0,往后每递增一个page,depth会加1;

    hiwat代表 high water mark

    • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
    • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
    • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
    • 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
    • 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
      若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时内存如下图
    示意图

    图中的情况,这一页再加入一个autorelease对象就要满了(也就是next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。

    所以,向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置

    3.创建自动释放池

    void* objc_autoreleasePoolPush()内部实际调用的是AutoreleasePoolPage::push()函数,其实现如下:

    //  objc_autoreleasePoolPush()内部实际调用
    void * objc_autoreleasePoolPush(void)
    {
        if (UseGC) return nil;
        return AutoreleasePoolPage::push();
    }
    
    //  AutoreleasePoolPage::push()的 push方法
    
    static inline void *push() 
        {
            id *dest = autoreleaseFast(POOL_SENTINEL);
            assert(*dest == POOL_SENTINEL);
            return dest;
        }
    
    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()是找出当前的正在使用的page

    1. hotPage存在且未满,AutoreleasePoolPage对象作为自动释放池加入栈中

    2. hotPage存在且hotPage页面满了,AutoreleasePoolPage创建新的Page并把对象添加到栈中

    3. hotPage不存在。添加一个新的AutoreleasePoolPage页面添加对象。

    4. POOL_SENTINEL哨兵对象,在每次自动释放池创建时,进行插入作为标记,当释放的时候,通过栈顶一直找到哨兵所在位置的所有对象进行释放。
      hotPage不存在,执行的方法

       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);
        }
    

    3.2.hotPage存在且hotPage页面满,执行的方法

    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->full());
    
            do {
                if (page->child) page = page->child;
                else page = new AutoreleasePoolPage(page);
            } while (page->full());
    
            setHotPage(page);
            return page->add(obj);
        }
    
    3.3.hotPage存在且未满,执行添加对象
     id *add(id obj)
        {
            assert(!full());
            unprotect();
            id *ret = next;  // faster than `return next-1` because of aliasing
            *next++ = obj;
            protect();
            return ret;
        }
    

    综上可以看出在添加自动释放池,所有操作都是对双向堆栈AutoreleasePoolPage的一个创建和添加的操作。

    4.销毁自动释放池

    首先autoreleasepool的释放工作交给objc_autoreleasePoolPop方法,bjc_autoreleasePoolPop方法如下,自动释放主要交给AutoreleasePoolPage::pop(ctxt);进行

    void
    objc_autoreleasePoolPop(void *ctxt)
    {
        if (UseGC) return;
    
        // fixme rdar://9167170
        if (!ctxt) return;
    
        AutoreleasePoolPage::pop(ctxt);
    }
    

    自动释放的方法如下,更具传入的token,查找需要删除的那个页面,进行删除操作。

    static inline void pop(void *token) 
        {
            AutoreleasePoolPage *page;
            id *stop;
    
            if (token) {
                page = pageForPointer(token);
                stop = (id *)token;
                assert(*stop == POOL_SENTINEL);
            } else {
                // Token 0 is top-level pool
                page = coldPage();
                assert(page);
                stop = page->begin();
            }
    
            if (PrintPoolHiwat) printHiwat();
    
            page->releaseUntil(stop);
    
            // memory: delete empty children
            // hysteresis: keep one empty child if this page is more than half full
            // special case: delete everything for pop(0)
            // special case: delete everything for pop(top) with DebugMissingPools
            if (!token  ||  
                (DebugMissingPools  &&  page->empty()  &&  !page->parent)) 
            {
                page->kill();
                setHotPage(nil);
            } else if (page->child) {
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    

    释放自动释放池内内存,双向堆栈中,删除一个AutoreleasePoolPage,根据这个AutoreleasePoolPage对象找到,通过while循环找到AutoreleasePoolPage下方的对象,就像二叉树找到叶子节点。通过节点,首先记录这个节点的地址,找出这个节点的父节点。通过父节点把子节点置空,删除这个节点的指针指向,在通过delete删除对象A的内存空间。通过while循环,直到删除到最初的节点。

    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);
        }
    

    系统创建的自动释放池

    • 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 了。

    也就是说AutoreleasePool创建是在一个RunLoop事件开始之前(push),AutoreleasePool释放是在一个RunLoop事件即将结束之前(pop)。
    AutoreleasePool里的Autorelease对象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease对象的释放是在AutoreleasePool释放时。

    其他Autorelease相关知识点

    • 使用容器的block版本的枚举器时,内部会自动添加一个autoreleasePool
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 
    { 
          // 这里被一个局部@autoreleasepool{ }包围着
    }];
    
    • 普通for循环for in循环没有,所以,还是新版的block版本枚举器更加方便,但是性能还是for in循环最高

    • 下面三种情况是需要我们手动添加autoreleasepool

      • 如果你编写的程序不是基于 UI 框架的,比如:命令行工具;
      • for循环中遍历产生大量autorelease变量时,就需要手动添加加局部autoreleasePool来进行手动干预
      • 如果你创建了一个子线程,一般会自定义继承自NSOperation的操作,在main方法中要加上@autoreleasepool{...},这段代码是在子线程上执行是无法访问主线程的自动释放池的,所以得自己创建

    相关文章

      网友评论

          本文标题:AutoreleasePool的原理和实现

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