AutoreleasePool解析

关键点

  1. 双向链表 AutoreleasePoolPageData
  2. TLS 线程局部存储?
  3. HotPage和ColdPage各是什么?
  4. objc_autoreleasePoolPush做了什么?
  5. objc_autorelease做了什么?
  6. objc_autoreleasePoolPop做了什么?
  7. 哨兵的作用

1.双向链表 AutoreleasePoolPageData

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    ...
    __unsafe_unretained id *next; // 栈顶指针
    pthread_t const thread; // 所在线程
    AutoreleasePoolPage * const parent;// 前指针
    AutoreleasePoolPage *child;// 后指针
    uint32_t const depth; // 深度 也可以称为页序号
    ...
};

class AutoreleasePoolPage : private AutoreleasePoolPageData {
    ...
}

2.TLS 线程局部存储

存储与取值

// 通过key获取值
static inline void *tls_get_direct(tls_key_t k)
{ 
    ASSERT(is_valid_direct_key(k));
 
    if (_pthread_has_direct_tsd()) {
        return _pthread_getspecific_direct(k);
    } else {
        return pthread_getspecific(k);
    }
}

//通过key设置值
static inline void tls_set_direct(tls_key_t k, void *value) 
{ 
    ASSERT(is_valid_direct_key(k));

    if (_pthread_has_direct_tsd()) {
        _pthread_setspecific_direct(k, value);
    } else {
        pthread_setspecific(k, value);
    }
}

_pthread_has_direct_tsd其实就是区分是模拟器还是真机

模拟器

pthread_getspecific, pthread_setspecific - thread-specific data management

pthread_setspecific(pthread_key_t value)将value的副本存储于一数据结构中,并将其与调用线程以及key相关联

pthread_setspecific()设置的value取出

实现同一个线程中不同函数间共享数据

真机

存储函数

__attribute__((always_inline))
static __inline__ void*
_os_tsd_get_direct(unsigned long slot)
{
    return _os_tsd_get_base()[slot];
}

__attribute__((always_inline))
static __inline__ int
_os_tsd_set_direct(unsigned long slot, void *val)
{
    _os_tsd_get_base()[slot] = val;
    return 0;
}

_os_tsd_get_base函数定义如下

__attribute__((always_inline, pure))
static __inline__ void**
_os_tsd_get_base(void)
{
#if defined(__arm__)
    uintptr_t tsd;
    __asm__("mrc p15, 0, %0, c13, c0, 3\n"
                "bic %0, %0, #0x3\n" : "=r" (tsd));
    /* lower 2-bits contain CPU number */
#elif defined(__arm64__)
    uint64_t tsd;
    __asm__("mrs %0, TPIDRRO_EL0\n"
                "bic %0, %0, #0x7\n" : "=r" (tsd));
    /* lower 3-bits contain CPU number */
#endif

    return (void**)(uintptr_t)tsd;
}

__asm 关键字用于调用内联汇编程序

结果值为0xFFFFFF8?

即每个线程会开辟专门的内存来存储当前线程正在使用的自动释放池,即自动释放池只和所属线程有关,和其他线程无关

__PTK_FRAMEWORK_OBJC_KEY3

固定key值来源

//Keys 40-49 for Objective-C runtime usage
...
#define __PTK_FRAMEWORK_OBJC_KEY3      43
...

3. HotPage和ColdPage各是什么?

3.1 HotPage

static inline AutoreleasePoolPage *hotPage()
{
   //当前使用的page
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key);
    //占位符
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    //检查
    if (result) result->fastcheck();
    return result;
}

如果是占位符,那么Page为nil,否则就是当前正在使用的Page

3.2 ColdPage

static inline AutoreleasePoolPage *coldPage()
{
    AutoreleasePoolPage *result = hotPage();
    if (result) {
        //循环找到第一页
        while (result->parent) {
            result = result->parent;
            result->fastcheck();
        }
    }
    return result;
}

如果是占位符,就是nil

如果自动释放池有数据,ColdPage就是第一个Page

4. objc_autoreleasePoolPush做了什么?

#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
#   define POOL_BOUNDARY nil

static inline void *push() {
    ...
    dest = autoreleaseFast(POOL_BOUNDARY);
    ...
}

4.1 占位符状态

当一个自动释放池只是新建(push时),并没有存入任何数据时,使用了一个占位符符表示Page已经创建,而不会真正的创建一个Page 节约内存


static inline id *autoreleaseFast(id obj) {
    ...
    return autoreleaseNoPage(obj);
    ...
}

static __attribute__((noinline)) id *autoreleaseNoPage(id obj) {
    ...
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
        return setEmptyPoolPlaceholder();
    }
    ...
}

static inline id* setEmptyPoolPlaceholder() {
    ...
    // 设置占位符
    tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
    // 返回占位符
    return EMPTY_POOL_PLACEHOLDER;
}

也就是,当一个自动释放池从没有对象进入时,他所存储的值是指向值为1的指针

4.1.1 占位符状态在hotPage中的处理

当没有对象进入释放池 获取的page都为空

static inline AutoreleasePoolPage *hotPage()
{
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
    tls_get_direct(key);
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    ...
}
4.1.2 占位符状态在pop中的处理

当自动释放池从未使用时,只需要把占位符的空间置空即可

static inline void pop(void *token) {
    ...
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        
        page = hotPage();
        if (!page) {// 当是占位符(1)时,page为空
            //置空TLS中存储的占位符
            return setHotPage(nil);
        }
        ...
    }
    ...
}

在处于占位符状态(空自动释放池)时,不想要创建Page;而自动释放池pop,也不需要额外释放page,从而节约内存

4.2 当Page不处于占位符状态时

static inline id *autoreleaseFast(id obj)
{
    ...
    //obj = nil
    if (page && !page->full()) {
        return page->add(obj);
    } else if (page) {
        return autoreleaseFullPage(obj, page);
    }
    ...
}

autoreleaseFullPage最终也是调用add函数

//obj = nil
id *add(id obj) {
    ...
    id *ret;
    ...
    ret = next;
    
    *next++ = obj;
    ...
    return ret;
}

也就是此时,Push操作是在Page中入栈了一个nil

5. objc_autorelease做了什么?

static inline id autorelease(id obj){
    ...
    id *dest __unused = autoreleaseFast(obj);
    ...
    return obj;
}

static inline id *autoreleaseFast(id obj) {
    //取到当前使用的Page 占位符状态时为空
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {//Page存在并且Page不满
        return page->add(obj);
    } else if (page) {//Page存在并且Page已经满页
        return autoreleaseFullPage(obj, page);
    } else {//page为空
        return autoreleaseNoPage(obj);
    }
}

5.1 占位符状态

static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
    bool pushExtraBoundary = false;
    
    //如果是占位符状态
    if (haveEmptyPoolPlaceholder()) {
        pushExtraBoundary = true;
    }
    ...
    
    //创建新的Page,父指针指向空
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    //修改线程存储,成为HotPage
    setHotPage(page);
    
    //占位符状态,先入栈nil  哨兵队长
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    
    //入栈对象
    return page->add(obj);
}

5.2 Page存在并且Page已经满页

AutoreleasePoolPage(AutoreleasePoolPage *newParent) :AutoreleasePoolPageData(begin(),
                            objc_thread_self(),
                            newParent,
                            newParent ? 1+newParent->depth : 0,
                            newParent ? newParent->hiwat : 0)
{
    /*
    AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
    : ...,parent(_parent), child(nil),...
     设置新节点的父节点以及一些参数
     */
    
    
    ...
    if (parent) {
        ...
        //设置新节点的父节点的子节点
        parent->child = this;
        ...
    }
    ...
}

static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    ...
    
    do {
        //有子节点
        if (page->child) page = page->child;
        //创建新的Page
        else page = new AutoreleasePoolPage(page);
    } while (page->full());
    
    //设置HotPage
    setHotPage(page);
    return page->add(obj);
}

5.3 Page存在并且Page不满

id *add(id obj) {
    ...
    id *ret;
    ...
    //临时保存
    ret = next;
    //赋值,next,栈满时,栈顶元素为空
    *next++ = obj;
    ...
    return ret;
}

6. objc_autoreleasePoolPop做了什么?

static inline void
pop(void *token)
{
    AutoreleasePoolPage *page;
    id *stop;
    
    //占位符状态
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        page = hotPage();
        if (!page) {
            //清空占位符
            return setHotPage(nil);
        }
        
        //取到第一页Page
        page = coldPage();
        token = page->begin();
    } else {//
        //根据地址取到page
        page = pageForPointer(token);
    }

    stop = (id *)token;
    ...

    return popPage<false>(token, page, stop);
}

出栈指定元素之后的所有元素,并且根据情况选择是否保留子节点

//删除Page
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
//    if (allowDebug && PrintPoolHiwat) printHiwat();
    //出栈到指定元素
    page->releaseUntil(stop);
    
    ...
    //释放子页
    if (page->child) {
        //page元素个数没有超过限制的一半 不需要保留Page子节点
        if (page->lessThanHalfFull()) {
            //删除节点
            page->child->kill();
        }
        else if (page->child->child) {//page元素个数超过限制的一半 保留Page的子节点
            page->child->child->kill();
        }
    }
}

出栈元素并release

//release直到某个对象
void releaseUntil(id *stop)
{
    while (this->next != stop) {
        //找到需要出栈的page
        AutoreleasePoolPage *page = hotPage();
        while (page->empty()) {
            //满栈
            page = page->parent;
            setHotPage(page);
        }
        
        ...
        //获取栈顶元素
        id obj = *--page->next;
        
        //栈顶位置置为0xA3
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        ....
        
        //release
        if (obj != POOL_BOUNDARY) {
            ...
            objc_release(obj);
        }
    }
    
    //目标页页设为HotPage
    setHotPage(this);
    ...
}

7. 哨兵的作用

没有哨兵时

@autoreleasepool {
    [obj1 autorelease];
    [obj2 autorelease];
    [obj3 autorelease];
    //AutoreleasePoolPage[...,obj1,obj2,obj3,栈顶(没有值)]
    
    @autoreleasepool {
        [obj4 autorelease];
        [obj5 autorelease];
        //AutoreleasePoolPage[...,obj1,obj2,obj3?,obj4,obj5,栈顶(没有值)]
    }
    
    @autoreleasepool {
        [obj6 autorelease];
        [obj7 autorelease];
        //AutoreleasePoolPage[...,obj1,obj2,obj3?,obj6,obj7,栈顶(没有值)]
        
    }
    //AutoreleasePoolPage[...,obj1,obj2,obj3,栈顶(没有值)]
    
}
//AutoreleasePoolPage[...,栈顶(没有值)]

当没有哨兵时,执行Pop,因为Page在同一个线程内是公用的,如果没有特殊处理,Page并不知道obj4或者obj6出栈之后,obj3是否应该出栈?

而正常情况,每个自动释放池都是一组数据添加,出栈时也应该一组的出栈

有哨兵时

@autoreleasepool {//objc_autoreleasePoolPush()
    [obj1 autorelease];
    [obj2 autorelease];
    [obj3 autorelease];
    //AutoreleasePoolPage[...,nil,obj1,obj2,obj3,栈顶(没有值)]
    
    @autoreleasepool {//objc_autoreleasePoolPush
        [obj4 autorelease];
        [obj5 autorelease];
        //AutoreleasePoolPage[...,nil,obj1,obj2,obj3,nil,obj4,obj5,栈顶(没有值)]
        
    }//objc_autoreleasePoolPop
    
    @autoreleasepool {//objc_autoreleasePoolPush
        [obj6 autorelease];
        [obj7 autorelease];
        //AutoreleasePoolPage[...,nil,obj1,obj2,obj3,nil,obj6,obj7,栈顶(没有值)]
        
    }
    //AutoreleasePoolPage[...,nil,obj1,obj2,obj3,栈顶(没有值)]
    
}//objc_autoreleasePoolPop

//AutoreleasePoolPage[...,栈顶(没有值)]

每当push时,先将哨兵对象(nil)入栈进去,然后再入栈其他对象,出栈时可以根据nil判断而不会使出栈对象紊乱

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

推荐阅读更多精彩内容