iOS - 内存管理(二)autoreleasepool

自动释放池

先看一份关于autoreleasepool的编译代码。

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

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结构体,它拥有一个构造函数和一个析构函数。
很明显,在main()函数中,被自动释放池所包含的区域被当做为一块局部的代码块,进入代码块调用autoreleasepool的构造函数,离开作用域时会调用析构函数释放局部变量。

AutoreleasePoolPageData

上面的构造函数和析构函数都是来自于AutoreleasePoolPage这个结构体内,而AutoreleasePoolPage又是继承自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(__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)
    {
    }
};

AutoreleasePoolPage究竟是什么?结合源码给出的注释它有以下几个特点:

  • 线程的自动释放池是一个指针堆栈。
  • 栈里面的指针要么是对象指针,要么就是POOL_BOUNDARY,这里理解为边界、哨兵。
  • 释放池的token是指向POOL_BOUNDARY
  • 释放池的栈是以page为单位的双向链表结构。
  • 页的深度由depth标记。
    总结一下就是,自动释放池是与线程相对应的双向链表,链表节点是以page为单位。

AutoreleasePoolPage的初始化

AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),
                                objc_thread_self(),
                                newParent,
                                newParent ? 1+newParent->depth : 0,
                                newParent ? newParent->hiwat : 0){
}

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

自动释放池初始化的时候

  • _next指向的是最新添加的 autoreleased 对象的下一个位置,初始化时指向
    begin() 。
  • thread指向当前线程。
  • parent指向父节点,第一个节点为nil。
  • child直接点,最后一个节点为nil。
  • depth节点深度,默认是0.
  • hiwat代表最大入栈数量标记。
    autorelease对象结构体

也就是说,autorelease对象内部管理的变量是放在栈结构中,栈顶初始化位置是begin()也就是结构体的末尾位置,也叫边界。每一次压栈,栈指针往高位移动8位。
每一个autorelease对象管理的栈节点叫做page,每一页的大小是4096个字节,autorelease对象自身成员变量占用56个字节外,余下空间可以压入(4096-56)/8 = 505个对象指针,(除第一页有一个特殊标记只有504个外,objc779模拟器环境)。
最后一页会标记为hotPage(),其他页标记为coldPage()

autorelease push和pop

对象是如何进入自动释放池的栈队列中?
AutoreleasePoolPage压入一个对象指针基本流程:

  1. 判断page是否存在,是否满了。
  2. page不存在,新创建一个page,并依据节点关系初始化。
  3. 如果page满了就会新建下一页。
  4. 如果page没满,就把*next中存入压入的对象指针,并且next++
      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);
        }
    }

    id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

pop的操作和push类似,原理类似,只不过反过来执行的时候,对每一个对象都执行了objc_release(obj);函数释放对象,同时page自身也会被处理掉。

不同的线程,autoreleasepool不同,autoreleasepool嵌套的时候,不会创建多个page,但会有多个边界。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354