前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OC底层探索28-autoreleasepool自动释放池OC底层探索28-autoreleasepool自动释放池

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

作者头像
用户8893176
发布2021-08-09 11:12:20
6180
发布2021-08-09 11:12:20
举报
文章被收录于专栏:小黑娃Henry

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

代码语言:javascript
复制
int main(int argc, const char * argv[]) {
    @autoreleasepool {        
    }
    return 0;
}

1、autoreleasepool结构

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

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

代码语言:javascript
复制
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结构体

代码语言:javascript
复制
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 父类
代码语言:javascript
复制
/***********************************************************************
   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第一页的第一个对象插入,防止在释放时越界;

代码语言:javascript
复制
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<false>(token, page, stop)

代码语言:javascript
复制
template<bool allowDebug>
    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 释放当前页
代码语言:javascript
复制
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 释放页内对象
代码语言:javascript
复制
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. 然后倒着释放页,一个个释放。

补充:

代码语言:javascript
复制
_objc_autoreleasePollPrint()
  • 可以通过该命令打印当前autoreleasepool中的情况;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/7/12 下,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、autoreleasepool结构
  • 2、autoreleasePool结构体
    • 2.1 AutoreleasePoolPageData 父类
      • AutoreleasePoolPageData参数说明
      • child和parent就说明AutoreleasePoolPageData是一个双向链表;
  • 2、autoreleasepool 入栈&出栈
    • 2.1 objc_autoreleasePoolPush 入栈
      • 2.2 objc_autoreleasePoolPop 出栈
        • 2.2.1 kill 释放当前页
        • 2.2.2 kill 释放页内对象
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档