06底层学习_自动释放池

203 阅读4分钟

源码

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
  • 解析:

这个结构体的构造函数里调用objc_autoreleasePoolPush, 析构函数里调用objc_autoreleasePoolPop.那么我们就来分析一下objc_autoreleasePoolPush和objc_autoreleasePoolPop.

push和pop的源码

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

NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
  • 解析:

自动释放池的push和pop其实是对AutoreleasePoolPage的push和pop的封装.既然如此,我们就从AutoreleasePoolPage开始看起.

AutoreleasePoolPage

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
	magic_t const magic; // 4 * 4 = 16
	__unsafe_unretained id *next; // 8
	pthread_t const thread; // 8
	AutoreleasePoolPage * const parent; // 8
	AutoreleasePoolPage *child; // 8
	uint32_t const depth; // 4
	uint32_t hiwat; // 4

	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)
	{
	}
};
#define I386_PGBYTES            4096            /* bytes per 80386 page */
#define PAGE_SIZE               I386_PGBYTES
#define PAGE_MAX_SIZE           PAGE_SIZE
#define PAGE_MIN_SIZE           PAGE_SIZE
  • 解析:

从结构可以看出自动释放池是一个由AutoreleasePoolPage组成的双向链表.parent和child就是构造双向链表的指针. AutoreleasePoolPage属性大小为16 + 4 * 8 + 2 * 4 = 56 AutoreleasePoolPage 大小为4096 一页最多存储(4096 - 56) / 8 个临时变量

自动释放池用处

  • 产生大量的临时变量时
  • 自己创建辅助线程时

push

static inline void *push() 
    {
        id *dest;
        if (slowpath(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;
    }
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);
        }
    }
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);
    }
static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // ...省略

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

  • 有hotpage并且page没满.
    • page->add(obj)把对象加进page里.
      • page->add方法其实就是压栈,然后移动栈顶指针.
  • 有hotpage 并且满了
    • autoreleaseFullPage初始化一个新的page并且设置为hotpage
      • autoreleaseFullPage方法其实是遍历page直到找到一个没满的fullPage或者创建一个新的page.
    • page->add(obj)把对象加进page里
  • 无hotpage
    • autoreleaseNoPage创建一个page
      • autoreleaseNoPage表示一个page都没有,会创建第一个page.这个page是没有parent的.
    • 设置hotpage
    • 加POOL_BOUNDARY进对象page里
    • page->add(obj);把对象加进page里

pop

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        
		// ... 省略n多
		page = pageForPointer(token);
        stop = (id *)token;

        return popPage<false>(token, page, stop);
    }
template<bool allowDebug>
    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();
            }
        }
    }
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
    }
  • pageForPointer获取当前token所在的AutoreleasePoolPage.
    • 通过操作内存地址获得指针的偏移量.(将指针与页面大小4096取模)
  • releaseUntil(stop)一直释放栈中的对象直到stop
    • 移动栈顶指针, 一个个释放obj直到POOL_BOUNDRY. 一次pop等于是释放POOL_BOUNDRY到POOL_BOUNDRY之间的对象们.
    • 一般情况下 stop == token == POOL_BOUNDRY. 其实token可以是别的对象.只不过我们不那么用而已.

Runloop和autoreleasepool的关系

  • viewdidload里打断点. po [NSRunloop currentRunLoop];

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

总结

  • AutoReleasePool是双向链表. 每一页为一个Page, Page大小为4096.
  • 一个Page能存(4096 - 56) / 8 = 505 个变量.
  • Runloop有两个Observer监听autoreleasepool.
    • Entry时push,
    • 休眠时pop和push.
    • exit时pop

参考

自动释放池和Runloop