美文网首页
Autorelease解读

Autorelease解读

作者: 偶尔登南山 | 来源:发表于2019-04-23 11:19 被阅读0次

            Autorelease翻译过来就是自动释放,什么是自动释放,怎么自动释放,什么时候自动释放.这些是本文的研究重点.自动释放首先要从autoreleasepool说起.

    autoreleasepool是什么?

    通过clang编译器重写以下我们的Objective-C代码:

    // autoreleasepool
        @autoreleasepool {
            
        }
    

    这里我们定义了一个autoreleasepool对象,查看一下clang编译器重写后的源码:

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
        }
    
    // __AtAutoreleasePool声明结构
    struct __AtAutoreleasePool {
      __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
      ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
      void * atautoreleasepoolobj;
    };
    

            可以看到通过__AtAutoreleasePool结构构造了一个 __autoreleasepool实例,__AtAutoreleasePool()方法被调用即执行atautoreleasepoolobj = objc_autoreleasePoolPush()方法;析构时,即超出__AtAutoreleasePool的作用域,调用~__AtAutoreleasePool()方法,即即执行的是objc_autoreleasePoolPop(atautoreleasepoolobj)方法.其过程可以细分为以下过程(以objc-loadmethod.mm中调用load方法为例):

    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        loadMethodLock.assertLocked();
    
        // Re-entrant calls do nothing; the outermost call will finish the job.
        if (loading) return;
        loading = YES;
    
        // push
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        // pop
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    因此我们可以初步总结autoreleasepool的执行过程主要有:
    1)objc_autoreleasePoolPush()构造;
    2)autorelease操作;
    3)objc_autoreleasePoolPop(void *ctxt)析构
    而objc_autoreleasePoolPush()和objc_autoreleasePoolPop(void *ctxt)内部分别调用的是:

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

    可以看到分别调用的是AutoreleasePoolPage中的成员方法;AutoreleasePoolPage,在NSObject.mm文件中:

    // MARK: - AutoReleasepool class
    class AutoreleasePoolPage 
    {
        // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
        // pushed and it has never contained any objects. This saves memory 
        // when the top level (i.e. libdispatch) pushes and pops pools but 
        // never uses them.
    #   define EMPTY_POOL_PLACEHOLDER ((id*)1)
    
    #   define POOL_BOUNDARY 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;
        
        // next指针指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
        id *next;
        
        // 指向当前线程
        pthread_t const thread;
        
        // 父节点
        AutoreleasePoolPage * const parent;
        
        // 子节点
        AutoreleasePoolPage *child;
        
        // 节点深度
        uint32_t const depth;
        uint32_t hiwat;
    }
    

            查看autoreleasepool的源码可以看出,autoreleasepool没有单独的内存结构,它是以 AutoreleasePoolPage为单个节点组成的双向链表来实现的.autoreleasePoolPage中一些成员变量:

    magic:校验autoreleasepoolpage结构完整性;
    next:指向最新添加的autoreleased对象的下一个位置;
    thread:指向当前线程;
    parent:指向父节点,第一个节点parent为nil(相当于表头);
    child:指向子节点,最后一个节点child为nil(相当于表尾);
    

    push()方法

    push()方法也就是创建一个autoreleasepoolpage实例的过程:

    // push
        static inline void *push() 
        {
            id *dest;
            if (DebugPoolAllocation) {
                
                // 每一个autoreleasepool对象开始于一个新的autoreleasepoolpage
                // 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;
        }
    

    其逻辑过程:

    // 创建一个page逻辑
        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);
            }
        }
    

    其中每个page最多存储有4096个bytes:

    #define I386_PGBYTES        4096        /* bytes per 80386 page */
    #define I386_PGSHIFT        12      /* bitshift for pages */
    
    #define PAGE_SIZE       I386_PGBYTES
    

    当第一次创建的时候传入的是nil,所以调用autoreleaseNoPage()函数,创建一个新的page:

    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());
    
            bool pushExtraBoundary = false;
            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", 
                             pthread_self(), (void*)obj, object_getClassName(obj));
                objc_autoreleaseNoPool(obj);
                return nil;
            }
            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.
            AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
            setHotPage(page);
            
            // Push a boundary on behalf of the previously-placeholder'd pool.
            if (pushExtraBoundary) {
                page->add(POOL_BOUNDARY);
            }
            
            // Push the requested object or pool.
            return page->add(obj);
        }
    

            push()方法类似于autoreleasepool的初始化方法,先创建一个autoreleasepoolpage节点,然后想page中添加一个nil对象占位.

    dest = autoreleaseFast(POOL_BOUNDARY);
    define POOL_BOUNDARY nil
    

    autorelease()方法

    查看一下NSObject.h提供的autorelease方法接口,其调用栈:

    - (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
    
    // Replaced by ObjectAlloc
    - (id)autorelease {
        return ((id)self)->rootAutorelease();
    }
    
    // MARK: - autorelease 实现
    // Base autorelease implementation, ignoring overrides.
    inline id 
    objc_object::rootAutorelease()
    {
        if (isTaggedPointer()) return (id)this;
        if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
    
        return rootAutorelease2();
    }
    
    __attribute__((noinline,used))
    id 
    objc_object::rootAutorelease2()
    {
        assert(!isTaggedPointer());
        return AutoreleasePoolPage::autorelease((id)this);
    }
    
    

    可以看到autorelease最终调用的是AutoreleasePoolPage中的成员方法autorelease();

    // autorelease
        static inline id autorelease(id obj)
        {
            assert(obj);
            assert(!obj->isTaggedPointer());
            id *dest __unused = autoreleaseFast(obj);
            assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
            return obj;
        }
    

    AutoreleasePoolPage中的成员方法autorelease()内部调用的是:

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

    其逻辑是:
    1)先判断当前page存在并且没满,则将released obj 添加到当前page;
    2)如果当前page已满,则创建一个新的page,并将当前page的child指针指向new page;把released obj 添加到new page中;

    // MARK: - 创建一个new page
        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);
        }
    

    3)如果当前page不存在,则直接创建一个new page,并把released obj添加到new page中;没有page意味着autoreleasepool没有进行push操作或者autoreleasepool已经push操作了,但是没有添加任何released的obj或者添加的是nil对象,则创建一个空的autoreleasepool进行占位;

    // autoreleasepool中没有page
        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());
    
            bool pushExtraBoundary = false;
            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) {//如果没有创建一个autoreleasepool,则直接抛异常
                // 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", 
                             pthread_self(), (void*)obj, object_getClassName(obj));
                objc_autoreleaseNoPool(obj);
                return nil;
            }
            else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {//如果对象为空,并非debug环境,创建一个空的autore占位
                // 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.
            AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
            setHotPage(page);
            
            // Push a boundary on behalf of the previously-placeholder'd pool.
            if (pushExtraBoundary) {
                page->add(POOL_BOUNDARY);
            }
            
            // 将released obj添加到page中
            // Push the requested object or pool.
            return page->add(obj);
        }
    

    以上三个过程都有执行add()操作,具体看一下add():

    // 向page中添加released操作
        id *add(id obj)
        {
            assert(!full());
            unprotect();
            
            // 将next节点赋值给ret
            id *ret = next;  // faster than `return next-1` because of aliasing
            
            // next指针增大一个位置
            *next++ = obj;
            protect();
            return ret;
        }
    

    向一个对象发送一条 -autorelease消息,其实就是将这个对象加入到AutoreleasePoolPage中next指针指向的位置,然后next增大一个位置.

    pop()方法

    pop操作上面已经提到了是autoreleasepool的析构函数也就是page的销毁函数.

    // pop
        static inline void pop(void *token) 
        {
            AutoreleasePoolPage *page;
            id *stop;
    
            // 如果当前obj是nil
            if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
                // Popping the top-level placeholder pool.
                if (hotPage()) {
                    // Pool was used. Pop its contents normally.
                    // Pool pages remain allocated for re-use as usual.
                    pop(coldPage()->begin());
                } else {
                    // Pool was never used. Clear the placeholder.
                    setHotPage(nil);
                }
                return;
            }
    
            // 根据token获取page
            page = pageForPointer(token);
            stop = (id *)token;
            if (*stop != POOL_BOUNDARY) {
                if (stop == page->begin()  &&  !page->parent) {
                    // Start of coldest page may correctly not be POOL_BOUNDARY:
                    // 1. top-level pool is popped, leaving the cold page in place
                    // 2. an object is autoreleased with no pool
                } else {
                    // Error. For bincompat purposes this is not 
                    // fatal in executables built with old SDKs.
                    return badPop(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();
                }
            }
        }
    

            pop()函数有个参数是push()操作的返回值即next指针指向内存地址.然后拿到这个地址遍历链表遍历当前page,然后向page中的obj发送release消息,直到next指向nil:

    // MARK: - 释放obj
        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) {
                // 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
                
                // 获取page的父指针
                while (page->empty()) {
                    page = page->parent;
                    setHotPage(page);
                }
    
                page->unprotect();
                
                // 根据next指针缩小一个位置获取obj
                id obj = *--page->next;
                memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
                page->protect();
    
                if (obj != POOL_BOUNDARY) {
                    
                    // 发送release 消息
                    objc_release(obj);
                }
            }
    
            setHotPage(this);
    
    #if DEBUG
            // we expect any children to be completely empty
            for (AutoreleasePoolPage *page = child; page; page = page->child) {
                assert(page->empty());
            }
    #endif
        }
    

    总结

            AutoreleasePoolPage是双链表的一个节点,其内部有可以理解为一个存储released obj地址的堆栈容器,每一个新加入的released obj被添加到这个容器里面,然后有一个next指针指向最后被加入的obj地址的下一个位置.如果当前page SIZE 满了,创建一个新的page. pop()操作就是获取到page中的每一个obj,给他们发送release()消息.

    编译后源码库

    编译后的源码放在Github, 如果对你有帮助,请给一个star吧!

    博客地址&相关文章

    博客地址: https://waitwalker.cn/

    系列文章:

    1. Runtime源码编译

    2. objc_object解读

    3. Method解读

    4. Class解读

    5. Ivar objc_property_t Protocol解读

    6. Block解读

    7. Retain&Release解读

    8. Autorelease解读

    9. Weak解读

    10. msgSend()解读

    参考文献

    《Objective-C高级编程:iOS与OS X多线程和内存管理》(Pro multithreading and memory management for iOS and OS X)--译者: 黎 华

    相关文章

      网友评论

          本文标题:Autorelease解读

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