AutoReleasePool

在很久之后再次开始看基础,这一次,看一下 AutoReleasePool,AutoReleasePool 里面涵盖了很多知识点,是一个验证自己知识体系的好帮手。

一 简介

AutoReleasePool是自动释放池,打造了一个类似于C语言中,局部变量出了作用域变量就自动释放的效果。在MRC时代就已经有了,在ARC下,开发者直接使用不再那么多,但是系统内部使用非常多。
在 ARC 下,不能直接使用 NSAutoReleasePool 对象了,采用如下方式使用

@autoreleasepool {
}

下面就总结下 AutoReleasePool 特性及实现原理。

二 特性

首先要了解 autorelease,在MRC时代,使用 retain/release 管理对象引用计数。autorelease 和 release 类似,表示引用计数-1,但是时机是在当前 autorelease drain 之后。在 ARC 中,不再显示调用 drain,对象 autorelease 实际为出了 @autoreleasepool 花括号作用域时。
AutoReleasePool 是线程隔离的,在不同线程中,互不影响。如果线程开启了 runloop,那么在 runloop 的 Entry(即将进入Loop), BeforeWaiting(准备进入休眠) ,Exit(即将退出Loop) 时,都会将该线程的所有 AutoReleasePool 清空或者重新创建 AutoReleasePool 并加入待释放对象。
以主线程为例,APP 的 main 函数,就有一个大的 AutoReleasePool。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
    }
    return 0;
}

三 实现

首先,珠玉在前,引用别人整理好的 blog :https://zhuanlan.zhihu.com/p/150569825
关键点:

  1. 源码编译:用 https://github.com/LGCooci/objc4_debug 中已经修改好的可编译源码
  2. AutoReleasePool 的 push 和 pop 操作
  3. 以 AutoReleasePoolPage 为节点的双向链表存储结构
  4. AutoReleasePoolPage 结构
  5. 一个 @autoreleasepool 和 AutoReleasePoolPage 的对应关系
  6. 实现结构中的优化点

clang 重写成 cpp 代码

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

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

引出了关键俩函数 objc_autoreleasePoolPushobjc_autoreleasePoolPop
push 过程中,核心函数是 autoreleaseFast

 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage(); // 双向链表中的最后一个 Page
        if (page && !page->full()) {// 如果当前 Page 存在且未满
            return page->add(obj);      // 将 autorelease 对象入栈,即添加到当前 Page 中;
        } else if (page) { // 如果当前 Page 存在但已满
            return autoreleaseFullPage(obj, page); // 创建一个新的 Page,并将 autorelease 对象添加进去
        } else {// 如果当前 Page 不存在,即还没创建过 Page
            return autoreleaseNoPage(obj);      // 创建第一个 Page,并将 autorelease 对象添加进去
        }
    }

结构

正常的变量,都是存在于堆上,大小固定。而所有的 AutoReleasePool 对象,都是共用同一块区间。他的结构是一个双向链表,每一个节点是一个 AutoReleasePoolPage,有 parent & child 指针,可以向上向下寻找其他 page 对象,每个 Page 大小 4K 字节。

Page 的数据区是一个栈,用来存储加入到 pool 里的对象引用,并在合适的时机遍历调用 release。每个 pool 都会用边界(哨兵) POOL_BOUNDARY (nil)隔开,所以当 pop 操作时,就是将到上一个 POOL_BOUNDARY 间的对象都出栈,并 release 。

当前使用的 Page 称之为 hotpage,第一个 Page 被称为 coldpage 。当增加一个 autorelease 调用,该对象的引用,都会被压入 hotpage 栈顶,当栈满了,就顺着 child 去下一 Page(没有就新建)寻找可存储的位置,并更新hotpage 位置。

hotpage 指针也是被存储在当前线程的 thread_local variables 中,是每次使用AutoReleasePool 可以通过 key 直接找到的入口。

黑魔法之Thread Local Storage
Thread Local Storage(TLS)线程局部存储,目的很简单,将一块内存作为某个线程专有的存储,以key-value的形式进行读写.
在返回值身上调用objc_autoreleaseReturnValue方法时,runtime将这个返回值object储存在TLS中,然后直接返回这个object(不调用autorelease);同时,在外部接收这个返回值的objc_retainAutoreleasedReturnValue里,发现TLS中正好存了这个对象,那么直接返回这个object(不调用retain)。
于是乎,调用方和被调方利用TLS做中转,很有默契的免去了对返回值的内存管理。这样就省去了两个操作:autorelease和外部的一次retain操作,对于性能提高很多。

双链表

image

每个Page

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)
    {
    }
};

详情

image

优化

(当仅推入一个池且从未包含任何对象时,EMPTY_POOL_PLACEHOLDER就会存储在TLS中。当顶层(即libdispatch)推送并弹出池但从不使用它们时,这样可以节省内存。)在应用加载的时候 loadimage 时有很多的空 autoreleasepool 在加载(具体不清楚是什么)。这个场景是,所有的 autoReleasePage 都没有对象,就用这个来占位,而不是一个真正的 Page 对象。

在一个 Pool 中对象调用完 release 后,会将所有的空 page 清除掉。但如果页面已满一半,则保留一个空孩子。

线程/runloop相关

和 runloop 的关系,尚不明确。仅知道如果线程如果没 runloop ,在使用 pool 的时候也会创建。如果有 runloop,就会自动创建。

何时进行的线程绑定?
在可以看到的调用处(有一些代码没有开源), map_images_nolock 时,会调用 init 静态方法,里面产生了一些和线程的关联,在线程销毁时,也会将 pool 销毁。目前知道,在主线程当中,注册了两个监听器,监听 Entry(即将进入Loop), BeforeWaiting(准备进入休眠) ,Exit(即将退出Loop) 时,都对 pool 进行了相应的 pop push 操作。

    static void init()
    {
        int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                             AutoreleasePoolPage::tls_dealloc);
        ASSERT(r == 0);
    }

    static void tls_dealloc(void *p) 
    {
        if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
            // No objects or pool pages to clean up here.
            return;
        }

        // reinstate TLS value while we work
        setHotPage((AutoreleasePoolPage *)p);

        if (AutoreleasePoolPage *page = coldPage()) {
            if (!page->empty()) objc_autoreleasePoolPop(page->begin());  // pop all of the pools
            if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
                // pop() killed the pages already
            } else {
                page->kill();  // free all of the pages
            }
        }
        
        // clear TLS value so TLS destruction doesn't loop
        setHotPage(nil);
    }

Q:关于不同的对象,为何不能触发 autorelease,这个可能和 taggedPoint 有关,待了解。

四 参考

加入pool的对象条件
很全面的介绍blog
结构图的引用
带有内存地址的单个Page结构
有 __builtin_return_address、TLS 的介绍, sunnyxx的blog,图片缺失
magic校验内容

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

推荐阅读更多精彩内容