autoreleasePool总结

作者: YY_Lee | 来源:发表于2019-03-24 14:01 被阅读0次

    先举个例子,下面这段代码是在非ARC环境下运行:

    @autoreleasepool {
         AutoRelaseObj *obj = [[AutoRelaseObj new] autorelease];
         NSLog(@"\n in pool -----%@",obj);
    }
    NSLog(@"\n out pool");
    

    通过clang编译后的代码如下:

    { 
        __AtAutoreleasePool __autoreleasepool; 
        AutoRelaseObj *obj = ((AutoRelaseObj *(*)(id, SEL))(void *)objc_msgSend)((id)((AutoRelaseObj *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AutoRelaseObj"), sel_registerName("new")), sel_registerName("autorelease"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_0aa177_mi_0,obj);
     }
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_0aa177_mi_1);
    

    下面是__AtAutoreleasePool的声明:

    struct __AtAutoreleasePool {
        __AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
            atautoreleasepoolobj = objc_autoreleasePoolPush();
        }
        ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
            objc_autoreleasePoolPop(atautoreleasepoolobj);
        }
        void * atautoreleasepoolobj;
     };
    

    结合上面的代码,上面的示例的实现大致如下:

     atautoreleasepoolobj = objc_autoreleasePoolPush();
     AutoRelaseObj *obj = [[AutoRelaseObj new] autorelease];
     NSLog(@"\n in pool -----%@",obj);
     objc_autoreleasePoolPop(atautoreleasepoolobj);
     NSLog(@"\n out pool");
    

    解析来我们分别看下objc_autoreleasePoolPush和objc_autoreleasePoolPop的具体实现;

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

    objc_autoreleasePoolPush和objc_autoreleasePoolPop又分别调用AutoreleasePoolPage的push和pop函数;我们先看下AutoreleasePoolPage这个类的结构:

    class AutoreleasePoolPage 
    {
    #   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;
        id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    
    // 成员变量后的第一个入栈位
      id * begin() {
            return (id *) ((uint8_t *)this+sizeof(*this));
        }
      // 最后入栈位
        id * end() {
            return (id *) ((uint8_t *)this+SIZE);
        }
        // next指向起始位时表示page已经没有autorelease对象了
        bool empty() {
            return next == begin();
        }
        // next指向末位时表示page已经没有存满没有多余空间了
        bool full() { 
            return next == end();
        }
        // 存储一半空间了
        bool lessThanHalfFull() {
            return (next - begin() < (end() - begin()) / 2);
        }
    }
    

    AutoreleasePoolPage是AutoreleasePool的核心类,AutoreleasePool的实现就是由多个AutoreleasePoolPage以双向链表的形式连接在一起。下面解释下各成员变量:

    • SIZE:每个page的最低容量为PAGE_MAX_SIZE,为4096 /* bytes per 80386 page */
    • COUNT:当前page包含多少个autorelease对象
    • next:指向最后加入autoreleasepool的对象
    • thread:autoreleasepool所在线程,AutoreleasePool是按线程一一对应的
    • parent:父结点,指向前一个AutoreleasePoolPage对象
    • child:子结点,指向后一个AutoreleasePoolPage对象
    • depth:链表的深度,节点个数
    • hiwat:数据容纳上限

    接着我们看下AutoreleasePoolPage的push函数实现:

    static inline void *push() 
        {
            id *dest;
            if (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;
        }
    //POOL_BOUNDARY定义如下
    #   define POOL_BOUNDARY nil
    

    先来看下autoreleaseFast函数,这是autoreleasePool的关键步骤:

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

    autoreleaseFast函数先获取hotPage,若当前hotPage存在且没满的情况下直接添加obj。若hotPage存在但没有存储空间了,调用autoreleaseFullPage函数,创建新的page并将对象添加该page中。若page不存在,调用autoreleaseNoPage函数,新建page将对象添加到该page中。

    • 获取hotPage
    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;
        }
    

    hotPage当前在使用的page,这里使用了tls的方法来存取当前的page。TLS是线程局部存储(Thread Local Storage)的缩写,在oc中通过这两个方法来使用,键值对存取:

    static inline void *tls_get_direct(tls_key_t k) 
    { 
        assert(is_valid_direct_key(k));
    
        if (_pthread_has_direct_tsd()) {
            return _pthread_getspecific_direct(k);
        } else {
            return pthread_getspecific(k);
        }
    }
    static inline void tls_set_direct(tls_key_t k, void *value) 
    { 
        assert(is_valid_direct_key(k));
    
        if (_pthread_has_direct_tsd()) {
            _pthread_setspecific_direct(k, value);
        } else {
            pthread_setspecific(k, value);
        }
    }
    
    • 调用autoreleaseNoPage,创建page
      第一次调用push的时候,还没有page,所以通过hotPage()是获取不到的,此时会调用autoreleaseNoPage创建page:
    static __attribute__((noinline))
        id *autoreleaseNoPage(id obj)
        {
            assert(!hotPage());
    
            bool pushExtraBoundary = false;
            if (haveEmptyPoolPlaceholder()) {
                pushExtraBoundary = true;
            }
            else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
                _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) {
                return setEmptyPoolPlaceholder();
            }
    
            // 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);
        }
    

    第一次调用这个方法,haveEmptyPoolPlaceholder返回false,且传入的obj是POOL_BOUNDARY,所以代码会走到setEmptyPoolPlaceholder():

    static inline id* setEmptyPoolPlaceholder()
        {
            assert(tls_get_direct(key) == nil);
            tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
            return EMPTY_POOL_PLACEHOLDER;
        }
    

    这个方法调用后,再次调用autoreleaseNoPage时就会进入第一个if条件中,接着就会调用下面的方法创建一个page并设为hotPage,接着向page中添加一个边界值POOL_BOUNDARY,最后再把将要autorelease的对象添加进来。

    • page添加对象add(obj)
        id *add(id obj)
        {
            assert(!full());
            unprotect();
            id *ret = next;  // faster than `return next-1` because of aliasing
            *next++ = obj;
            protect();
            return ret;
        }
    

    将obj添加到page中,next向后移一位。

    • autoreleaseFullPage
      每个page只要4096字节,page存储满后调用autoreleaseFullPage新建一个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);
        }
    

    autoreleaseFullPage的实现可以看出,如果当前page有子节点则将子节点设为hotPage,并将对象添加到子节点的page中。如果没有子节点则新建一个page,设为hotPage,并将对象添加到该page中。

    现在我们看下当一个对象调用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;
        }
    

    对象调用autorelease,通过函数autoreleaseFast将对象加入到AutoreleasePoolPage中;所以autorelease方法并不是释放对象的方法,是将对象加入到AutoreleasePool中等待释放;

    那添加的到AutoreleasePool中的对象是怎样释放的呢?

    Runloop注册了两个关于AutoreleasePool的观察者,回调函数都是_wrapRunLoopWithAutoreleasePoolHandler。如下,第一个observer的activites值为0x1,监听的是KCFRunloopEntry事件,其回调函数会调用_objc_autoreleasePoolPush() 创建自动释放池。第二个observer的activites值为0xa0,监听的是kCFRunLoopBeforeWaiting和kCFRunLoopExit;监听到kCFRunLoopBeforeWaiting在回调函数中调用_objc_autoreleasePoolPop和_objc_autoreleasePoolPush函数销毁旧的autoreleasePool并创建新的autoreleasePool;监听到kCFRunLoopExit在回调函数中调用_objc_autoreleasePoolPop函数释放autoreleasePool;

    observers = (
        "<CFRunLoopObserver 0x600001d48280 [0x10f59cb68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11218b1b1), context = <CFArray 0x6000022716e0 [0x10f59cb68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fca70800058>\n)}}"
    
        "<CFRunLoopObserver 0x600001d481e0 [0x10f59cb68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11218b1b1), context = <CFArray 0x6000022716e0 [0x10f59cb68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fca70800058>\n)}}"
    )
    

    pop函数的实现:

        static inline void pop(void *token) //token是栈顶指针
        {
            AutoreleasePoolPage *page;
            id *stop;
    
            #pragma mark --- part1----
            if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
                // Popping the top-level placeholder pool.
                if (hotPage()) {
                    pop(coldPage()->begin());
                } else {
                    // Pool was never used. Clear the placeholder.
                    setHotPage(nil);
                }
                return;
            }
    
            #pragma mark --- part2----
            page = pageForPointer(token);// 通过栈顶的地址找到对应的page
            stop = (id *)token;
            if (*stop != POOL_BOUNDARY) {
                if (stop == page->begin()  &&  !page->parent) { 
                } else {
                    return badPop(token);
                }
            }
            if (PrintPoolHiwat) printHiwat();
    
            #pragma mark --- part3----
            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容量小于一半时,kill掉child
                    page->child->kill();
                }
                else if (page->child->child) {// 当前page容量大于一半时,保留child,kill掉child的child
                    page->child->child->kill();
                }
            }
        }
    

    这个方法分为三个部分:

    • part1:判断token是否是EMPTY_POOL_PLACEHOLDER,这是autoreleasepool首次push的时候返回的,最顶层的pop会执行这一部分;
    • part2:在非ARC情况下,在新创建的线程中不使用autoreleasepool,直接调用autorelease方法时会出现token不等于POOL_BOUNDARY的情况。
    • part3:对page中的对象调用release方法,接着kill child;
    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);
        }
    

    kill函数将调用者的所有子节点置为nil;

        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) {//释放page中的autorelease对象,直到遇到POOL_BOUNDARY为止
                // 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();
                id obj = *--page->next;
                memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
                page->protect();
    
                if (obj != POOL_BOUNDARY) {
                    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
        }
    

    releaseUntil函数从当前page的栈顶开始给autorelease对象发送release消息,当前page发送完毕,再向父节点中的autorelease对象发送release消息,直到首节点为止。

    相关文章

      网友评论

        本文标题:autoreleasePool总结

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