美文网首页
AutoreleasePool解析

AutoreleasePool解析

作者: honzon_0 | 来源:发表于2022-03-06 22:35 被阅读0次

    关键点

    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判断而不会使出栈对象紊乱

    相关文章

      网友评论

          本文标题:AutoreleasePool解析

          本文链接:https://www.haomeiwen.com/subject/yuxgrrtx.html