Ios@IONICiOSOC 底层

autoreleasePool总结

2019-03-24  本文已影响0人  YY_Lee

先举个例子,下面这段代码是在非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以双向链表的形式连接在一起。下面解释下各成员变量:

接着我们看下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中。

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);
    }
}
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的对象添加进来。

    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向后移一位。

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

这个方法分为三个部分:

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消息,直到首节点为止。

上一篇 下一篇

猜你喜欢

热点阅读