AutorealeasePool 实现原理

@autoreleasepool 编译后会变成

void * atautoreleasepoolobj = objc_autoreleasePoolPush();
// 中间代码
objc_autoreleasePoolPop(atautoreleasepoolobj);

这里要记录一下,objc_autoreleasePoolPush函数的返回值 传给了objc_autoreleasePoolPop()

objc_autoreleasePoolPush 、 objc_autoreleasePoolPop

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

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

push 和 pop函数其实是调用的AutoreleasePoolPage的push 和 pop

AutoreleasePoolPage 类

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

上面是AutoreleasePoolPage类代码

1.AutoreleasePoolPage本质上是一个双向链表 分别有parent 和 child 指针。

2.每一个AutoreleasePoolPage大小固定为4096个字节 (虚拟内存一页的大小)

3.4096字节除了上面包含的各种变量(magic、next、thread、parent、child、depth、hiwat)外,剩下就用于存储autorealease对象指针

4.next指针记录下一个为空的地址, 每加入一个对象 next指针就会偏移一次。

5.介绍一下POOL_SENTINEL,其实就是一个宏定义  

#define POOL_SENTINEL nil

有了上面的概念后 分析一下push函数

static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}

Push的核心函数 autoreleaseFast

push 函数 调用了 autoreleaseFast函数 并传入了 标兵对象 ,也就是nil ,接着来看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);
   }
}

首先获取hotPage() 也就是当前最新创建的page

如果page没有满,将obj添加到page

如果page满了,新建一个newpage并将 page的child指向newchild, newchild的parent指向page (双向链表的基本操作),然后将obj加入到newpage里- 将newpage 设置为hotPage

如果压根儿就没有page, 那就新建一个page,也就是表头,此时有个操作是判断obj是不是标兵如果过是直接插入,否则在obj加入之前先加入一个标兵 - 防止pop时崩溃(这也是autoreleaseFullPage 和 autoreleaseNoPage的区别)。

static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    setHotPage(page);
    return page->add(obj);
}
static id *autoreleaseNoPage(id obj) {
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);

    if (obj != POOL_SENTINEL) {
        page->add(POOL_SENTINEL);
    }

    return page->add(obj);
}

接下来 看page->add(obj )做了啥

id *add(id obj) {
    id *ret = next;
    *next = obj;
    next++;
    return ret;
}

 将新加的对象存储地址返回

还记得一开始记录一下的地方吗,将push函数的返回值 作为参数传给了 pop

也就是将一个标兵(nil)的地址传给了 pop() 

 

接下来分享Pop函数 

static inline void pop(void *token) {
    AutoreleasePoolPage *page = pageForPointer(token);//获取token所在page
    id *stop = (id *)token;
    page->releaseUntil(stop); // 释放
    if (page->child) { // 移除后面链表
        if (page->lessThanHalfFull()) {
            page->child->kill();
        } else if (page->child->child) { 
            page->child->child->kill();
        }
    }
}

第一步、pageForPointer 获取token所在的page 【通过offse来计算】

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指针进行PoolPage大小SIZE(4096)取模->offset,然后用p减去offset就是AutoreleasePoolPage的首地址,也就是page的地址。fastcheck()是一个快速检测result是否是Page的方法,不再深入看。
 

第二步、releaseUntil 释放对象

void releaseUntil(id *stop) { // this 为当前标兵坐在的page
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();// 获取最新的page
        while (page->empty()) { // 如果是空的 往上遍历 找到非空的 并设置为hotpage
            page = page->parent;
            setHotPage(page);
        }
        page->unprotect(); // 解锁
        id obj = *--page->next; // 得到当前page的最新指针 拿到对象 next指针-1
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // autorealease对象指针置空
        page->protect();
        if (obj != POOL_SENTINEL) { // 对obj发送release
            objc_release(obj);
        }
    }
    setHotPage(this);
}

总结一下就是从hotpage的next指针开始释放到传入的对象,也就一开始说的标兵对象。

这也就是和一开始的代码对应起来了,push函数传入一个标兵并将地址传入pop函数,pop函数开始Realease对象到 标兵停止 行程一个闭合。代码粘过来 回顾一下

void * atautoreleasepoolobj = objc_autoreleasePoolPush();// 开始
// 中间代码 在这里写autoRealease 代码
objc_autoreleasePoolPop(atautoreleasepoolobj); // 结束

这种实现方式也能实现 释放池的嵌套

第三步、所有的autorealease指针置空并对obj发送realease消息以后,接下来要是释放token之后的page链表,也就是kill函数,这里做了一步判断,如果当前容量还没存储够一半,剩下的page全部清空,如果当前存储大于当前page的一半,留下childpage,将childpage的所有子page全部删除

if (page->child) { // 移除后面链表
        if (page->lessThanHalfFull()) { // 如果当前存储还不到一半 删除所有的child
            page->child->kill();
        } else if (page->child->child) { // 大于一半,保留childpage ,删除后面的page
            page->child->child->kill();
        }
    }

kill 函数

void kill() {
    AutoreleasePoolPage *page = this; 
    while (page->child) page = page->child; // 也就是链表的最后一个
    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        page = page->parent;
        if (page) { // 父page 的child 指向nil
            page->unprotect();
            page->child = nil;
            page->protect();
        }
        delete deathptr; // 释放当前page
    } while (deathptr != this); // 遍历条件 deathptr == this的时候跳出
}

 

 

以上就是全部了。。。 

你可能感兴趣的:(一点点儿。。。,iOS,AutoRealease,实现原理)