AutoreleasePool

133 阅读5分钟

临时变量什么时候释放

clang 发现编译器 @autoreleasepool编译成这个结构体。 创建的时候会调构造函数,超出作用域的时候回调析构函数。 atautoreleasepoolobj关联push和pop

自动释放池原理: AutoreleasePoolPage

Autorelease pool implementation
   A thread's autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is 
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When 
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added 
     and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased 
     objects are stored. 

1.先进后出 2. POOL_BOUNDARY 边界对象,用来区分AutoreleasePoolPage自己的成员变量和加进去的对象

3、双向链表 4、 tls 线程

自动释放池的结构属性 AutoreleasePoolPageData

struct AutoreleasePoolPageData
{
	magic_t const magic; // 16 用来校验 AutoreleasePoolPage 的结构是否完整
	__unsafe_unretained id *next; //8指向最新添加的 autoreleased 对象的下一个位置,初始化时指向begin() ;
	pthread_t const thread; // 8 指向当前线程
	AutoreleasePoolPage * const parent; //8 指向父结点,第一个结点的 parent 值为 nil ;
	AutoreleasePoolPage *child; //8 指向子结点,最后一个结点的 child 值为 nil ;
	uint32_t const depth; // 4 代表深度,从 0 开始,往后递增 1;
	uint32_t hiwat; // 4 代表 high water mark 最大入栈数量标记
	
	AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
		: magic(), next(_next), thread(_thread),
		  parent(_parent), child(nil),
		  depth(_depth), hiwat(_hiwat)
	{
	}
};

打印autoreleasePool,

发现自动释放池是一页一页的,每页容量505个对象,第一页会自动存放一个pool对象,就是边界。一页满了,会标记page(full),否则为page(hot),第一页(cold)标记

objc_autoreleasePoolPush

找到hotPage,没有就创建,有就添加,满就从新创建

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

add

  id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

autoreleaseFullPage

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

autoreleaseNoPage

    id *autoreleaseNoPage(id obj)
    {

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

示意图

objc_autoreleasePoolPop

    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        return popPage<false>(token, page, stop);
    }

popPage

释放表和对象

static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && 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();
            }
        }
    }

循环释放对象

从page页的最后一个一个类似递归往回释放objc_release(obj) page->empty(),next指针指向begin()函数的时候表示这一页释放完毕,取出上一页继续释放

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

@autoreleasepool 嵌套

嵌套 - 只会创建一个page - 但是有两个边界对象,表示一个autoreleasepool,的大小,所以边界对象的作用是重复利用这个表,表示2个作用域空间

2020-03-22 16:50:59.630491+0800 KCObjcTest[90615:10937555] objc = <NSObject: 0x101603e90>
objc[90615]: ##############
objc[90615]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[90615]: 2 releases pending.
objc[90615]: [0x104002000]  ................  PAGE  (hot) (cold)
objc[90615]: [0x104002038]  ################  POOL 0x104002038
objc[90615]: [0x104002040]       0x101603e90  NSObject
objc[90615]: ##############
2020-03-22 16:50:59.631795+0800 KCObjcTest[90615:10937874] obj = <NSObject: 0x101208aa0>
objc[90615]: ##############
objc[90615]: AUTORELEASE POOLS for thread 0x7000000cb000
objc[90615]: 3 releases pending.
objc[90615]: [0x104800000]  ................  PAGE  (hot) (cold)
objc[90615]: [0x104800038]  ################  POOL 0x104800038
objc[90615]: [0x104800040]  ################  POOL 0x104800040
objc[90615]: [0x104800048]       0x101208aa0  NSObject