AutoReleasePoolPage

AutoReleasePoolPage

类的定义

class AutoreleasePoolPage 
{
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;
    static size_t const SIZE = PAGE_MAX_SIZE;

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

成员变量解析:

  • key:用于创建线程的标识
  • SCRIBBLE:刷子,可以不需要关心
  • SIZE:4096Bytes
  • COUNT:4096/8
  • magic:用于验证 AutoReleasePoolPage 的完整性
  • next:栈顶指针
  • thread: 当前线程
  • parent: 父节点, AutoReleasePoolPage 是一个双向链表,这个parent也就是指向上一个节点
  • child: 子节点(下一个节点)
  • depth: 深度,标识这是第几个节点,从0开始计数
  • hiwat: high water mark 高水位线,用来报警内存占用

就如前面所说,AutoReleasePoolPage 他是一个双向链表,每个节点大小为4096Bytes,而且前面56Bytes是成员函数所占有的。而且每个节点中有一个堆栈,其中next指针是指向栈顶。

begin()end()两个方法分辨指向栈底和对象的结尾:

begin

id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}

end

id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

标准的进栈操作

add

id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next; 
    *next++ = obj;
    protect();
    return ret;
}

出栈

releaseUntil

void releaseUntil(id *stop) 
{        
    while (this->next != stop) {

        AutoreleasePoolPage *page = hotPage();

        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
}

这里的出栈跟普通出栈不太一样,因为这里的出栈会一直递归出栈到stop才会停止,这里的SCRIBBLE就是用来填写空白内存区的。

以上基本上算这个类的定义内容的解释。

那么这个类有什么作用呢?

用来管理实现垃圾自动回收。

怎么实现垃圾回收?

void autoReleaseTest(void)
{
    void *pool = objc_autoreleasePoolPush();
    
    ........
    
    objc_autoreleasePoolPop(pool);

}

每次有一个加一个AutoReleasePool,编译器自动回报我们解析成以上的形式。那么看一下objc_autoreleasePoolPush()objc_autoreleasePoolPop(pool)方法的实现

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

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

也就是要先看看:

push

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

这里可以看到Debug实现的方式不太一样,

Debug是每次push都会创建一个新的page

然而非Debug却不是

这应该是为了Release节省内存设计的。

所以Debug环境会比Release环境内存耗费的多一点吧。

至于这里的POOL_BOUNDARY 是被define成一个nil对象的,用处就是一个哨兵对象的作用。

说一下哨兵的作用:

哨兵节点通常被用在链表和遍历树中,他并不拥有或引用任何被数据结构管理的数据。常常用哨兵节点来替代null,这样的好处有:

  • 增加操作的速度
  • 降低算法的复杂性和代码的大小
  • 增加数据结构的鲁棒性

简单的说哨兵就是为了简化边界条件的处理而存在的。

autoreleaseFast

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

根据不同的page状态来进行不同的操作

  • page存在且未满的时候:直接add object
  • page存在但是已经满了的时候:先创建一个page,且当前pagechild指向新page,新pageparent指向当前page,然后add object
  • page不存在的时候:创建一个新page,再add object

autoreleaseFullPage

static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *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);
}

hotpage()这个是当前正在使用的page

autoreleaseNoPage

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();
    }
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    
    return page->add(obj);
}

add

id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next;  
    *next++ = obj;
    protect();
    return ret;
}

这里的next是指向一个空指针的,注意一下!
以上就是push操作了。

pop

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

    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        if (hotPage()) {
            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) {

        } else {
            return badPop(token);
        }
    }

    if (PrintPoolHiwat) printHiwat();

    page->releaseUntil(stop);

    if (DebugPoolAllocation  &&  page->empty()) {
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        page->kill();
        setHotPage(nil);
    } 
    else if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

pop操作比push要麻烦一些!
简单的说就是:

  • 找到token所在的page
  • token之后的所有对象,发送release消息
  • 杀掉多余的page

pageForPointer

static AutoreleasePoolPage *pageForPointer(const void *p) 
{
    return pageForPointer((uintptr_t)p);
}

static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
{
    AutoreleasePoolPage *result;
    uintptr_t offset = p % SIZE;

    assert(offset >= sizeof(AutoreleasePoolPage));

    result = (AutoreleasePoolPage *)(p - offset);
    result->fastcheck();

    return result;
}
  • 先获得偏移量
  • 通过当前 p减去偏移量获取 page指针
  • 检测完整性
  • 返回page

releaseUntil

void releaseUntil(id *stop) 
{        
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();

        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

    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        assert(page->empty());
    }
#endif
}
  • 获取hotpage
  • 判断是否为空,非空进入下一步,为空获取parent,并且设置parenthotpage,然后进入上一步
  • 获取栈顶对象,清空栈顶指针,向栈顶对象发送release,下移next指针
  • 判断next是否等于stop,相等进入下一步,不相等回到第一步
  • 设置hotpage

在debug环境中因为每次push会创建一个新的page,每次pop会把空的pagekill掉,所以不存在空的page

kill

if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
  • page使用量小于一半,杀掉pagechild之后的所有page
  • page的使用量大于一半且存在孙子的时候,杀掉孙子之后的所有page
  • page的使用量大于一半且不存在孙子的时候,保留child

这里为什么要保留一个child,可能大家觉得很奇怪,其实是为了节省开销,因为如果大于一半的栈被占用了之后很有可能,马上需要新建新的page对象。

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);
}
  • 先找到双向链表最后一个节点
  • 然后从最后一个节点开始删除
  • 直到删除到当前节点(包括当前节点)

总结

那么到目前为止AutoReleasePoolPage里面主要的流程已经讲完了。其他的一些方法都是无关大雅的东西了。

实际上AutoReleasePoolPage只是做一个导师的角色,他并不会去销毁对象(自己除外),但是他回去告诉他管理的对象现在该release一下。

有问题的地方望各位大能指出来!看到我会改正的哦!~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容