IOS-自动释放池

1,524 阅读5分钟

AutoreleasePool

  • 自动释放池(NSAutoreleasePool):An object that supports Cocoa’s reference-counted memory management system.
  • 使用示例:
    • 在MRC环境下
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // Code benefitting from a local autorelease pool.
    [pool release];
    
    • ARC环境下
    @autoreleasepool {
     // Code benefitting from a local autorelease pool.
    }
    
  • 在MRC环境下也是可以使用@autoreleasepool块的

通过clang查看底层实现

  • 通过命令xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m生成main.cpp文件
  • 查看autoreleasepool对应的代码
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    }
    return 0;
}
  • 继续搜索__AtAutoreleasePool,看到__AtAutoreleasePool是一个结构体
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
  • __AtAutoreleasePool __autoreleasepool;相当于执行了__AtAutoreleasePool的构造函数和析构函数
atautoreleasepoolobj = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(atautoreleasepoolobj);

通过汇编观察执行流程

  • 直接在main函数打下断点,进到调试界面
  • 加上符号断点objc_autoreleasePoolPush,再次运行进入断点
  • 可以看到objc_autoreleasePoolPush是在libobjc.dylib的,可以通过objc源码查看实现

AutoreleasePool的底层实现

objc_autoreleasePoolPush

  • objc_autoreleasePoolPush的实现代码
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
  • AutoreleasePoolPage是一继承自AutoreleasePoolPageData的类
/***********************************************************************
   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. 
     
  自动释放池实现
  
  线程的自动释放池是指针的堆栈。
  每个指针要么是要释放的对象,要么是POOL_BOUNDARY
    自动释放池边界。
  池令牌是指向该池的POOL_BOUNDARY的指针。 什么时候
    弹出池,释放比哨点更热的每个对象。
  堆栈分为两个双向链接的页面列表。 页面已添加
    并根据需要删除。
  线程本地存储指向新自动发布的热点页面
    对象被存储。
**********************************************************************/
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;

public:
	static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
		PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
		PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif
    
private:
......
}
  • 从上面也可以看到page页的最大空间为4096。
  • AutoreleasePoolPageData的结构
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
	magic_t const magic;
	__unsafe_unretained id *next;
	pthread_t const thread;
	AutoreleasePoolPage * const parent;
	AutoreleasePoolPage *child;
	uint32_t const depth;
	uint32_t hiwat;

	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)
	{
	}
};
  • 再回到AutoreleasePoolPage::push()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;
}
  • 往下执行到autoreleaseFast函数
static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {// 有page,且没有满
        return page->add(obj);  //直接在page里增加
    } else if (page) {  //有page,并且满了,一层层page往下找,找到page在将对象加进去
        return autoreleaseFullPage(obj, page);
    } else {// 没有page ,新建一个page,标记位hot,将对象添加进去
        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的实现
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());//往下找page,直到找到没有满的page,如果没有就新建一个page

    setHotPage(page);
    return page->add(obj);
}
  • 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", 
                     objc_thread_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);
}
  • new AutoreleasePoolPage的初始化方法,begin()的实现
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    AutoreleasePoolPageData(begin(),
                            objc_thread_self(),
                            newParent,
                            newParent ? 1+newParent->depth : 0,
                            newParent ? newParent->hiwat : 0)
{ 
    if (parent) {
        parent->check();
        ASSERT(!parent->child);
        parent->unprotect();
        parent->child = this;
        parent->protect();
    }
    protect();
}

id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}
  • 自动释放池在初始化的时候
    • _next指向的是最新添加的autoreleased对象的下一个位置,初始化时指向begin() 。
    • thread指向当前线程。
    • parent指向父节点,第一个节点为nil。
    • child直接点,最后一个节点为nil。
    • depth节点深度,默认是0.
    • hiwat代表最大入栈数量标记。 自动释放池

objc_autoreleasePoolPop

  • objc_autoreleasePoolPop出栈,pop的实现
static inline void
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();
        }
    }
}
  • pop的实现和push相似,只是流程相反,释放完最后一页之后,将此也删除,然后遍历上层page,释放里面的对象直到遇到哨兵。真个释放池就完全释放了

_objc_autoreleasePoolPrint

  • 我们通过将_objc_autoreleasePoolPrint声明到main函数之中,来打印自动释放池的数据
#import <Foundation/Foundation.h>

extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (int i = 0; i < 5 ; i++) {
            NSObject *obj = [[NSObject alloc] autorelease];
        }
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

  • 打印数据可以看到我们加入到池中的5个对象及一个哨兵对象,哨兵的偏移地址0x38正好是结构体的大小56,从上文知道每个page的最大size为4096,所以每一页可以存放505个自动释放的对象,第一页有一个哨兵,所以存放504个
  • 通过修改代码查看
  • 自动释放池的嵌套
  • 可以看到嵌套的自动释放池并没有新建一个page,而是增加了一个哨兵。