OC底层探索28-autoreleasepool自动释放池

对于autoreleasepool一定不陌生,但内部结构是什么呢?

int main(int argc, const char * argv[]) {
    @autoreleasepool {        
    }
    return 0;
}

1、autoreleasepool结构

@autoreleasepool做了什么操作呢?再次祭出clang

clang命令:
clang -rewrite-objc main.m -o main.cpp

int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool; }
    return 0;
}

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
  • 在oc中autoreleasepool在经过clang的编译之后,底层的实现是一个__AtAutoreleasePool结构体
  • 构造函数调用objc_autoreleasePoolPush(),析构函数objc_autoreleasePoolPop(atautoreleasepoolobj);这三个点就是探索的入口;
  • autoreleasepool也是一个对象;

2、autoreleasePool结构体

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;

    //一页大小4098
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT) 
    static size_t const SIZE = PAGE_MAX_SIZE

    // 构造函数
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),
                                objc_thread_self(),
                                newParent,
                                newParent ? 1+newParent->depth : 0,
                                newParent ? newParent->hiwat : 0)
    {
        ...
    }
    // 析构函数
    ~AutoreleasePoolPage() 
    {...}
}
  • thread_data_t保存当前线程信息,可以看到自动释放池与线程是有关的;
  • 一页的大小是4096字节,后面会验证这部分;
  • 构造函数newParent=nil也就是AutoreleasePoolPageData(begin(), objc_thread_self(),nil newParent, 0, 0)

2.1 AutoreleasePoolPageData 父类

/***********************************************************************
   Autorelease pool implementation

   A thread's autorelease pool is a stack of pointers.
   Each pointer is either an object to release, or POOL_BOUNDARY which is
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added
     and deleted as necessary.
   Thread-local storage points to the hot page, where newly autoreleased
     objects are stored.
**********************************************************************/

struct AutoreleasePoolPageData
{
    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

//对照:`AutoreleasePoolPageData(begin(), objc_thread_self(),nil newParent, 0, 0)`
    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

autoreleasepool特点:

  1. 线程的自动释放池是一个指针堆栈。
  2. 栈里面的指针要么是对象指针,要么就是POOL_BOUNDARY,这里理解为边界、哨兵。
  3. 释放池的栈是以page为单位的双向链表结构
  4. 页的深度由depth标记。
AutoreleasePoolPageData参数说明
  • _next:指向的是最新添加的 autoreleased 对象的下一个位置,初始化时指向
    begin() 。
  • thread:指向当前线程。
  • parent:指向父节点,第一个节点为nil。
  • child:直接点,最后一个节点为nil。
  • depth:节点深度,默认是0.
  • hiwat:代表最大入栈数量标记。
child和parent就说明AutoreleasePoolPageData是一个双向链表;

结构图:


  • 销毁对象:就是在autoreleasepool中声明的对象,每次入栈一个对象_next就会往后移动8个字节;
  • 一个释放池可能会存在多页(在内部对象非常多的情况下),最后一页会标记为hotPage(),其他页标记为coldPage();

2、autoreleasepool 入栈&出栈

在clang代码查看到的:* objc_autoreleasePoolPush()objc_autoreleasePoolPop(atautoreleasepoolobj)

2.1 objc_autoreleasePoolPush 入栈

源码调用流程,只看主分支代码:objc_autoreleasePoolPush -> AutoreleasePoolPage::push() -> autoreleaseFast(POOL_BOUNDARY) -> autoreleaseFast

POOL_BOUNDARY nil 哨兵对象

POOL_BOUNDARY在每一个autoreleasepool第一页的第一个对象插入,防止在释放时越界;

static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    // 1. 如果不满,则直接插入
    if (page && !page->full()) {
        return page->add(obj);
    } else if (page) {  // 2. 如果满了,创建新页并将当前页传入建立关系
        return autoreleaseFullPage(obj, page);
    } else {
        return autoreleaseNoPage(obj);
    }
}

id *add(id obj)
{
    id *ret = next;  // faster than `return next-1` because of aliasing
    // 对象赋值,并移动next
    *next++ = obj;
    return ret;
}

总体流程:

  • 第一次 push 方法是创建一个页,并设置为当前的焦点页(hotPage),并且把哨兵放进去做栈底
  • 每次向自动释放池中添加对象时,如果焦点页已满,会递归子页去找到未满的页来入栈对象;
  • 如果所有页都满了,会再创建一个页,并将上一个页的child 指向这个新的页,然后把新的页设置为焦点页

2.2 objc_autoreleasePoolPop 出栈

源码调用流程,只看主分支代码:objc_autoreleasePoolPop -> AutoreleasePoolPage::pop(ctxt) -> popPage(token, page, stop)

template
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
// 一口气释放页内所有对象
    page->releaseUntil(stop);

    // 释放页
    // memory: delete empty children
    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (allowDebug && 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();
        }
    }
}
2.2.1 kill 释放当前页
void kill() 
{
    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);
}
2.2.2 kill 释放页内对象
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) {
        //// 如果不是哨兵对象则继续释放
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            for (int i = 0; i < count + 1; i++) {
                objc_release(obj);
            }
#else
            objc_release(obj);
#endif
        }
    }
    setHotPage(this);
}
  • 释放时沿着parent这条路线向上释放,只到释放到第一页的哨兵对象结束;
  • 总体流程
    1. 遵循先进后出的原则,后进入的先释放,
    2. 先找焦点页的最大的next, next 逐个递减,倒着将对象一一释放,直到焦点页为空,
    3. 再沿着parent 找到上一个页,并设置为焦点页,重复第2步, 直到遇到哨兵对象停止。
    4. 然后倒着释放页,一个个释放。

补充:

_objc_autoreleasePollPrint()
  • 可以通过该命令打印当前autoreleasepool中的情况;

你可能感兴趣的:(OC底层探索28-autoreleasepool自动释放池)