ios进阶

RunTime源码阅读(八)之autoreleasepool

2020-01-26  本文已影响0人  某非著名程序员

1.autoreleasepool特性

  1. 在ARC时只要保证在@autoreleasepool{}包裹,变量超出函数的区域,不会被释放,直到@autoreleasepool结束被释放。
  2. 对于系统方法,系统会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。(结论来自于Objective-C高级编程+iOS与OS+X多线程和内存管理)。这个可以在objc-runtime源码中验证。如NSString中的stringWithFormat:,NSArray中的arrayWithObject。

2. 原理

autoreleasepool:以栈为节点的双链表结构。
@autoreleasepool{}相当于调用了objc_autoreleasePoolPush、objc_autoreleasePoolPop

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

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

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;

AutoreleasePoolPage是以链表的形式存储,parent与child组成双链表结构。
*next是真正存储obj的地方,是一个静态数组,控制一头进行添加与删除,形成栈的结构,SIZE是分配的大小。

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

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {//page存在且没有满
            return page->add(obj);//添加obj
        } else if (page) {//page存在且满了,需要分配新的页
            return autoreleaseFullPage(obj, page);
        } else {//page为空时
            return autoreleaseNoPage(obj);
        }
    }

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()) {//已经有空的pool占位
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            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);//添加对象
    }

objc_autoreleasePoolPush:初始化时会为setEmptyPoolPlaceholder分配一个占位。
当有嵌套时,pushExtraBoundary设置为YES,添加POOL_BOUNDARY哨兵对象,再添加obj对象。

stringWithFormat与arrayWithObject发生什么

2.1 stringWithFormat

stringWithFormat
__attribute__((aligned(16)))
id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->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;
    }

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {//page存在且没有满
            return page->add(obj);//添加obj
        } else if (page) {//page存在且满了,需要分配新的页
            return autoreleaseFullPage(obj, page);
        } else {//page为空时
            return autoreleaseNoPage(obj);
        }
    }

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

stringWithFormat会调用objc_autorelease-调用NSObject中的autorelease-调用autoreleaseFast-调用page->add(obj);

2.2 arrayWithObject

arrayWithObject与stringWithFormat一样调用了objc_autorelease-调用page->add(obj);

2.3 objc_autoreleasePoolPop

static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

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

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

static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
                result->fastcheck();
            }
        }
        return result;
    }

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为空时,查找parent为hotPage
                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结束时调用objc_autoreleasePoolPop-调用releaseUntil-出栈,调用objc_release释放对象

结论:

  1. @autoreleasepool遵循引用计数原理
  2. 系统的非alloc/new/copy/mutableCopy方法会调用autorelease方法,最终会调用page-add(obj)添加到队列中;释放时会调用objc_release。
  3. ARC多数情况下并不需要手动调用@autoreleasepool,但在访问大内存块时,如图片,可以用@autoreleasepool包裹,能做到及时释放。
上一篇 下一篇

猜你喜欢

热点阅读