美文网首页iOS开发
ios内存管理(四):Autorelease技术

ios内存管理(四):Autorelease技术

作者: 智小融 | 来源:发表于2017-12-24 22:36 被阅读138次

      前面章节提到内存释放时,经常会说到当超出变量作用域时,变量会被“自动”释放,其实这只是为了更加简单的说明这个过程。实际上,在ARC模式下是系统帮你自动插入了相应的release逻辑。这个流程有没有什么问题呢?
      请看如下代码:

    -(NSObject *)objFactory() {
        NSObject *obj = [[NSObject alloc];
        return obj;
    }
    

      这个函数生成了一个NSObject对象,并作为了返回值。那么在超出函数作用域前,要不要插入[obj release]呢?怎么样都不合适,如果插入,该函数的caller若用weak指针持有函数返回值,马上会将其置为nil;如果不插入,caller可能不知道要release这个对象,又会造成内存泄露。

      为了解决这个问题,引入了一个非常重要的技术,就是Autorelease。简单来讲,就是用autorelease来代替release,将要释放的对象先放入一个“释放池”,而不是马上释放。这个自动释放池,就是AutoreleasePool。

      AutoreleasePool是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

      AutoreleasePool是如何实现的呢?一个线程对应有一个Autoreleasepool,其结构是一个指针栈。栈中存放的指针指向加入需要release的对象或者POOL_SENTINEL(哨兵对象,用于分隔Autoreleasepool)。栈中指向POOL_SENTINEL的指针就是Autoreleasepool的一个标记。当Autoreleasepool进行出栈操作,每一个比这个哨兵对象后进栈的对象都会release。这个栈是由一个以page为节点双向链表组成,page根据需求进行增减。Autoreleasepool对应的线程存储了指向最新page(也就是最新添加autorelease对象的page)的指针。

      AutoreleasePoolPage的结构如下:

        magic_t const magic; // 魔数,校验用
        id *next; // 栈顶指针
        pthread_t const thread; // 当前线程
        AutoreleasePoolPage * const parent; // 父节点
        AutoreleasePoolPage *child; // 子节点
        uint32_t const depth; // 链表节点的个数
        uint32_t hiwat; // high water mark(最高水位标记)
    

      下面来看AutoreleasePool的几个关键函数实现:

       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;
        }
        static inline id *autoreleaseFast(id obj)
        {
            AutoreleasePoolPage *page = hotPage();    // 获取最新的page(即链表上最新的节点)
            if (page && !page->full()) {
                return page->add(obj);    // 在这个page存在且不满的情况下,直接将需要autorelease的对象加入栈中
            } else if (page) {
                return autoreleaseFullPage(obj, page);    // 在这个page已经满了的情况下,新建一个page并将obj对象放入新的page(即入栈)
            } else {
                return autoreleaseNoPage(obj);    // 在没有page的情况下,新建一个page并将obj对象放入新的page(即入栈)
            }
        }
        id *add(id obj)   // 入栈操作
        {
            assert(!full());
            unprotect();    // 解除保护
            id *ret = next;  // faster than `return next-1` because of aliasing
            *next++ = obj;    // 将obj入栈到栈顶并重新定位栈顶
            protect();    // 添加保护
            return ret;
        }
    
        static inline void pop(void *token)   // token指针指向栈顶的地址
        {
            AutoreleasePoolPage *page;
            id *stop;
    
            page = pageForPointer(token);   // 通过栈顶的地址找到对应的page
            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);   // 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象
    
            // 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();
                }
            }
        }
        static inline id autorelease(id obj)
        {
            assert(obj);
            assert(!obj->isTaggedPointer());
            id *dest __unused = autoreleaseFast(obj);   //  添加obj对象到自动释放池的链表栈中
            assert(!dest  ||  *dest == obj);
            return obj;
        }
    
    

      加入AutoreleasePool的对象,会在[pool drain]的时候release。那么drain方法到底在什么时候调用呢?这里又引出了一个Runloop的概念。对于每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。
      下一章,再详细研究Runloop到底是什么。

    相关文章

      网友评论

        本文标题:ios内存管理(四):Autorelease技术

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