iOS自动释放池的实现原理分析

199 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情

引用计数:
-当我们创建一个实例对象,它的引用计数为1;
-当我们向一个对象发送retain消息,它的引用计数+1;
-当我们向一个对象发送release消息,它的引用计数-1;
-当我们向一个对象发送autorelease消息,它的引用计数会在当前自动释放池的末尾-1;
-当一个对象的引用计数减到0,它的内存会被回收。

在MRC下如下代码:

@autoreleasepool{
    NSObject *obj = [[NSObject alloc] init] autorelease];
}

通过clang命令可以转换成cpp源代码,在代码中包含__AtAutoreleasePool结构体如下:

//自动释放池结构体
struct __AtAutoreleasePool {
    __AtAutoreleasePool(){/*构造函数*/
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool(){/*析构函数*/
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void *atautoreleasepoolobj;
};

该结构体有一个void *类型成员变量atautoreleasepoolobj,保存了构造函数中调用objc_autoreleasePoolPush()的返回值,在析构函数中调用objc_autoreleasePoolPop(atautoreleasepoolobj),将该值传递了进去。

搜索源码,可以看到objc_autoreleasePoolPush()内部调用了AutoreleasePoolPage::push(),objc_autoreleasePoolPop(ctxt)内部调用了AutoreleasePoolPage::pop(ctxt)

重点来到了AutoreleasePoolPageAutoreleasePoolPage继承自AutoreleasePoolPageData,它的结构如下:

struct AutoreleasePoolPageData{
    magic_t const magic; // 16
    __unsafe_unretained id *next; // 8
    pthread_t const thread; // 8
    AutoreleasePoolPage * const parent; // 8
    AutoreleasePoolPage *child; // 8
    uint32_t const depth; // 4
    uint32_t hiwat;  // 4
}

class AutoreleasePoolPage : private AutoreleasePoolPageData{}
  • magic:用来校验AutoreleasePoolPage的结构是否完整
  • next:指向栈顶,也就是最新入栈的autorelease对象的下一个位置
  • thread:指向当前线程
  • parent:指向父节点
  • child:指向子节点
  • depth:表示链表的深度,也就是链表节点的个数
  • hiwat:表示high water mark(最高水位标记)
    通过数据结构,我们可以看到AutoreleasePoolPage是一个双向链表结构, parent指向了父节点,child指向了子节点。那么整个的自动释放池到底是怎么运作起来的呢?接下来我们将重点分析一下AutoreleasePoolPage::push()AutoreleasePoolPage::pop(ctxt)这2个方法。
static inline void *push() {
    id *dest = autoreleaseFast(POOL_BOUNDARY);
    return dest;
}

去掉DebugPoolAllocation调试的if分支,内部调用了autoreleaseFast(POOL_BOUNDARY),返回了dest# define POOL_BOUNDARY nil,则表示push的时候传入了的POOL_BOUNDARY是一个nil,POOL_BOUNDARY也叫哨兵对象。

static inline id *autoreleaseFast(id obj){
    AutoreleasePoolPage *page = hotPage();//获取hotPage
    if (page && !page->full()) {
       //page存在且没有满,则添加obj(这里obj是POOL_BOUNDARY)
       return page->add(obj);
    } 
    else if (page) {
       //page存在且已满,传入obj和page(这里obj是POOL_BOUNDARY)
       return autoreleaseFullPage(obj, page);
    } 
    else {
        //page不存在的情况下
       return autoreleaseNoPage(obj);
    }
}

这个函数做的是一个初步判定的工作:如果当前hotPage存在且未满,则page-add(obj);如果page存在且已满则调用autoreleaseFullPage(obj, page)page不存在的情况下调用autoreleaseNoPage(obj)。按照逻辑来时首次应当是不存在hotPage的,所以我们先看看autoreleaseNoPage(obj)

static __attribute__((noinline)) id *autoreleaseNoPage(id obj){
    bool pushExtraBoundary = false;
    if (haveEmptyPoolPlaceholder()) {
        //是否有占位
        pushExtraBoundary = true;
    }
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
        //设置一个占位
        return setEmptyPoolPlaceholder();
    }

    //有占位后就初始化一个page。并设置为hotPage
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    
    //将哨兵对象加入作为边界的标记
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    
    //将请求的对象加入
    return page->add(obj);
}

打印obj对象如下:

(lldb) po obj
<__NSBundleTables: 0x1007217a0>