美文网首页收藏iosiOS RunLoop总结
iOS - AutoreleasePool - 底层篇

iOS - AutoreleasePool - 底层篇

作者: felix6 | 来源:发表于2020-07-05 19:58 被阅读0次

    [toc]

    参考

    AutoreleasePool 底层

    私有函数打印

    可以通过以下私有函数来查看自动释放池的情况:

    // extern 声明系统内部的函数
    extern void _objc_autoreleasePoolPrint(void);
    
    示例1
    extern void _objc_autoreleasePoolPrint(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool { //  r1 = push()
            Person *p1 = [[[Person alloc] init] autorelease];
            Person *p2 = [[[Person alloc] init] autorelease];
            @autoreleasepool { // r2 = push()
                Person *p3 = [[[Person alloc] init] autorelease];
                @autoreleasepool { // r3 = push()
                    Person *p4 = [[[Person alloc] init] autorelease]; 
                    _objc_autoreleasePoolPrint(); // 调用该私有函数
                } // pop(r3)
                
            } // pop(r2)
        } // pop(r1)
        return 0;
    }
    
    控制台输出如下:
    objc[1405]: ##############
    objc[1405]: AUTORELEASE POOLS for thread 0x1000d2dc0
    objc[1405]: 7 releases pending.
    objc[1405]: [0x10300d000]  ................  PAGE  (hot) (cold)
    objc[1405]: [0x10300d038]  ################  POOL 0x10300d038 // Boundary
    objc[1405]: [0x10300d040]       0x1007adcd0  Person
    objc[1405]: [0x10300d048]       0x1007acd90  Person
    objc[1405]: [0x10300d050]  ################  POOL 0x10300d050
    objc[1405]: [0x10300d058]       0x1007ab730  Person
    objc[1405]: [0x10300d060]  ################  POOL 0x10300d060
    objc[1405]: [0x10300d068]       0x1007acc50  Person
    objc[1405]: ##############
    
    示例2

    一页不够的情况

    int main(int argc, const char * argv[]) {
        @autoreleasepool { //  r1 = push()
            Person *p1 = [[[Person alloc] init] autorelease];
            Person *p2 = [[[Person alloc] init] autorelease];
            @autoreleasepool { // r2 = push()
                for (int i = 0; i < 600; i++) { // 这里 autorelease 了 600 个对象
                    Person *p3 = [[[Person alloc] init] autorelease];
                }
                @autoreleasepool { // r3 = push()
                    Person *p4 = [[[Person alloc] init] autorelease];
                    _objc_autoreleasePoolPrint();
                } // pop(r3)
            } // pop(r2)
        } // pop(r1)
        return 0;
    }
    
    控制台输出如下:
    objc[1461]: ##############
    objc[1461]: AUTORELEASE POOLS for thread 0x1000d2dc0
    objc[1461]: 606 releases pending.
    objc[1461]: [0x10300d000]  ................  PAGE (full)  (cold)
    objc[1461]: [0x10300d038]  ################  POOL 0x10300d038
    objc[1461]: [0x10300d040]       0x1005addd0  Person
    objc[1461]: [0x10300d048]       0x1005ace90  Person
    objc[1461]: [0x10300d050]  ################  POOL 0x10300d050
    objc[1461]: [0x10300d058]       0x1005ab830  Person
    .
    .
    .
    objc[1461]: [0x10300dff8]       0x1005b5d50  Person
    objc[1461]: [0x10300a000]  ................  PAGE  (hot) // 当前page; 上一页容量不够, 换页了
    objc[1461]: [0x10300a038]       0x1005b5d60  Person
    .
    .
    .
    objc[1461]: [0x10300a348]       0x1005b6380  Person
    objc[1461]: [0x10300a350]  ################  POOL 0x10300a350
    objc[1461]: [0x10300a358]       0x1005b6390  Person
    objc[1461]: ##############
    Program ended with exit code: 0
    

    源码分析:

    • objc4源码 NSObject.mm
    • clang重写 @autoreleasepool{}

    objc4源码

    https://opensource.apple.com/tarballs/objc4/

    clang重写

    OC代码:
    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
    转成C++
    int main(int argc, const char * argv[]) {
        {
            __AtAutoreleasePool __autoreleasepool;
            // 为简洁, 这里精简了string
            NSLog((NSString *)&__NSConstantStringImpl__var_xxxxxx_mi_0); 
        }
        return 0;
    }
    

    可以看到: @autoreleasepool {} 在编译时被转换为一个 __AtAutoreleasePool 类型的局部变量 __autoreleasepool ; ★

    __AtAutoreleasePool

    结构

    __AtAutoreleasePool 是个结构体, 定义如下: (定义在.cpp上部)

    struct __AtAutoreleasePool {
        // __AtAutoreleasePool的构造函数
        __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } 
        // __AtAutoreleasePool的析构函数
        ~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); } 
        // POOL_BOUNDARY 地址, push的返回值; pop的入参
        void * atautoreleasepoolobj;
    };
    
    实现原理

    可以看到, __AtAutoreleasePool 这个结构体会在:

    • 初始化时, 调用 objc_autoreleasePoolPush() 方法;
    • 在析构时, 调用 objc_autoreleasePoolPop() 方法;

    根据C++ 构造函数析构函数 的特点:

    • 自动局部变量的 构造函数, 是在程序执行到声明这个对象的位置时调用的;
    • 自动局部变量的 析构函数, 是在程序执行到离开这个对象的作用域时调用;

    所以:

    • 声明 __autoreleasepool 变量时, 构造函数 __AtAutoreleasePool() 被调用;

      即执行: atautoreleasepoolobj = objc_autoreleasePoolPush();

    • 出当前作用域时, 析构函数 ~__AtAutoreleasePool() 被调用;

      即执行: objc_autoreleasePoolPop(atautoreleasepoolobj);

    即, 在 __autoreleasepool 初始化和析构时, 分别调用 objc_autoreleasePoolPush()objc_autoreleasePoolPop() ; ★

    所以可以认为,

    当我们使用 @autoreleasepool{} 时, 编译器实际上将其转化为以下代码:

    int main(int argc, const char * argv[]) {
        {
            // 即将进入runloop, 调用push(), 创建pool, 
            void *atautoreleasepoolobj = objc_autoreleasePoolPush(); 
            
            NSLog((NSString *)&__NSConstantStringImpl__var_xxxxxx_mi_0);
            
            // 即将退出当前runloop时, 调用pop(), 释放pool
            objc_autoreleasePoolPop(atautoreleasepoolobj); 
        }
        return 0;
    }
    

    可见, @autoreleasepool{} 只是帮助我们封装了 objc_autoreleasePoolPush()objc_autoreleasePoolPop() 这两行代码, 让代码看起来更简洁。★

    至此, 我们可以分析出, 单个自动释放池的运行过程可简单概括为:

    objc_autoreleasePoolPush() 
    [object autorelease] ;
    objc_autoreleasePoolPop(void *)
    

    objc_autoreleasePoolPush()objc_autoreleasePoolPop() 这两个方法实际是对 AutoreleasePoolPage 对应的静态方法 push()pop() 的封装。

    // NSObject.mm
    void *objc_autoreleasePoolPush(void) {
        return AutoreleasePoolPage::push();
    }
    
    void objc_autoreleasePoolPop(void *ctxt) {
        AutoreleasePoolPage::pop(ctxt);
    }
    

    push()pop() 见下节 AutoreleasePoolPage 分析


    AutoreleasePoolPage ★

    简介

    AutoreleasePool 没有单独的结构, 而是由若干个 AutoreleasePoolPage双向链表的形式组合而成。 ★

    每个 AutoreleasePoolPage 的容量是 4096 字节 (0x1000, 4k, 也就是虚拟内存每个扇区的大小, 4k对齐由此而来), 除了用来存放它内部的成员变量(56B),剩下的空间(4040B)用来存放autorelease对象的地址;

    图示
    image
    结构
    class AutoreleasePoolPage
    // AutoreleasePoolPage 是一个 C++ 的类, 定义在 NSObject.mm 中
    class AutoreleasePoolPage : private AutoreleasePoolPageData {
        // 空池占位
    #   define EMPTY_POOL_PLACEHOLDER ((id*)1)  
        
        // 边界对象, 老版本源码中变量名是 POOL_SENTINEL(哨兵对象), 用来区别每个 AutoreleasePoolPage 边界
    #   define POOL_BOUNDARY nil 
        
        // 
        static pthread_key_t const key = AUTORELEASE_POOL_KEY; 
        // 0xA3A3A3A3 after releasing
        static uint8_t const SCRIBBLE = 0xA3;  
        // PAGE_MAX_SIZE = 4096, 每个 AutoreleasePoolPage 的大小
        static size_t const SIZE = PAGE_MAX_SIZE;  
        // 一个page里对象数
        static size_t const COUNT = SIZE / sizeof(id); 
    
        /// 下面这7个成员, 每个占8字节, 共56字节 ★
        
        magic_t const magic; // 用于对当前 AutoreleasePoolPage 进行完整性校验;
        
        // 指向最新添加的 autoreleased 对象的下一个位置, 初始化时指向 begin(); 
        // 指向下一个能够存放 autoreleased 对象地址的位置, 相当于一个游标
        id *next; 
        pthread_t const thread; // 指向当前线程;  AutoreleasePool 是按线程一一对应的 ★
        
        AutoreleasePoolPage * const parent; // 父结点, 指向前一个page, 第一个结点的 parent 值为 nil;
        AutoreleasePoolPage *child; // 子结点, 指向下一个page, 最后一个结点的 child 值为 nil;
        
        uint32_t const depth; // 链表的深度, 节点个数, 从 0 开始, 往后递增 1;
        uint32_t hiwat; // 代表 high water mark; 数据容纳的上限
        
        // ... 
    };
    
    struct AutoreleasePoolPageData
    // class AutoreleasePoolPage : private AutoreleasePoolPageData
    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 的空间被占满时, 会新建一个 AutoreleasePoolPage 对象, 连接链表, 后来的 autorelease 对象在新的 page 加入。★

    AutoreleasePoolPage 本质是结构体, 结构体中包含 parent 和 child 指针, parent 指向前一个 page, child 指向下一个 page, 从而构成双向链表。

    AutoreleasePoolPage 的组织是个栈, 结构成员 id *next 作为游标, 指向栈顶下一个为空的内存地址。★

    但他并不是指内存中的栈区;

    AutoreleasePoolPage 是个对象, 内存在堆区;

    begin()
    // 起始地址
    id * begin() {
        // 起始地址 + 自身大小
        return (id *) ((uint8_t *)this+sizeof(*this));
    }
    
    end()
    // 结束地址
    id * end() {
        // 起始地址 + 4096
        return (id *) ((uint8_t *)this+SIZE);
    }
    
    // 4096
    static size_t const SIZE =
    #if PROTECT_AUTORELEASEPOOL
            PAGE_MAX_SIZE;  // must be multiple of vm page size
    #else
            PAGE_MIN_SIZE;  // size and alignment, power of 2
    #endif
    
    hotPage()
    // 返回当前正在使用的page
    static inline AutoreleasePoolPage *hotPage() {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }
    
    push() ★
    // 将 POOL_BOUNDARY 压到 page 的内存中, 占8字节; 并返回其所在的内存地址
    // 存在嵌套的 pool 时, 每来到一个`{`, 就会压入1个 POOL_BOUNDARY 到 page 中, 每个 pool 对应一个 POOL_BOUNDARY  ★
    static inline void *push() {
        id *dest;
        // 一开始没有page, 创建新的 
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY); 
        } 
        // 本来就有page
        else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
    
    autoreleaseNewPage()
    // 创建新的 page; 入参 obj 为 POOL_BOUNDARY
    static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }
    
    autoreleaseFullPage()
    // 热页满了
    static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        ASSERT(page == hotPage());
        ASSERT(page->full()  ||  DebugPoolAllocation);
    
        do {
            if (page->child) page = page->child; // 
            else page = new AutoreleasePoolPage(page); // 如果满了 & 无子page, 传入当前page, 创建新page
        } while (page->full());
    
        setHotPage(page);
        // 将 POOL_BOUNDARY 压入 page
        return page->add(obj);
    }
    
    AutoreleasePoolPage()
    // 构造函数
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),
                                objc_thread_self(),
                                newParent,
                                newParent ? 1+newParent->depth : 0,
                                newParent ? newParent->hiwat : 0)
    { 
        if (parent) {
            parent->check();
            ASSERT(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
    }
    
    autoreleaseFast()
    // 本来就有page, 入参 obj 为 POOL_BOUNDARY 或者 autoreleased 对象
    static inline id *autoreleaseFast(id obj) {
        AutoreleasePoolPage *page = hotPage();
        
        if (page && !page->full()) { // 有热页, 没满
            // 添加到page
            return page->add(obj); 
        } else if (page) { // 有热页, 满了
            return autoreleaseFullPage(obj, page);
        } else { // 没有热页, 创建新页
            return autoreleaseNoPage(obj);
        }
    }
    
    autoreleaseNoPage()
    // 创建新页; 入参为 POOL_BOUNDARY
    static __attribute__((noinline)) id *autoreleaseNoPage(id obj) {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        ASSERT(!hotPage());
    
        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         objc_thread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }
    
        // We are pushing an object or a non-placeholder'd pool.
    
        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
    
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
    
        // Push the requested object or pool.
        return page->add(obj);
    }
    
    pop() ★
    // 传入之前 push() 所返回的 POOL_BOUNDARY 地址
    // 会从最后一个压入 page 的对象开始(栈顶), 依次调用其 release 方法, 直到这个 POOL_BOUNDARY
    // 存在嵌套pool时, 每离开一个 `}` 就会pop() 一次, 然后清理掉对应的 `{` 之后的autoreleased对象, 以及该 POOL_BOUNDARY
    static inline void pop(void *token) {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            page = pageForPointer(token);
        }
    
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }
    
        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }
    
        return popPage<false>(token, page, stop);
    }
    
    popPage()
    static void popPage(void *token, AutoreleasePoolPage *page, id *stop) {
        if (allowDebug && PrintPoolHiwat) printHiwat();
    
        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();
            }
        }
    }
    
    releaseUntil()
    // 迭代释放对象, 直到stop
    void releaseUntil(id *stop)  {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
    
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();
            // 处理跨页问题 ★
            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
    
            page->unprotect();
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();
            // 只要不是 POOL_BOUNDARY 就 release
            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }
    
        setHotPage(this);
    
    #if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
    #endif
    }
    
    
    autorelease ★
    autorelease
    + (id)autorelease {
        return (id)self;
    }
    
    // Replaced by ObjectAlloc
    - (id)autorelease {
        return _objc_rootAutorelease(self);
    }
    
    _objc_rootAutorelease()
    NEVER_INLINE id _objc_rootAutorelease(id obj) {
        ASSERT(obj);
        return obj->rootAutorelease();
    }
    
    rootAutorelease()
    // Base autorelease implementation, ignoring overrides.
    inline id  objc_object::rootAutorelease() {
        if (isTaggedPointer()) return (id)this;
        if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
    
        return rootAutorelease2();
    }
    
    rootAutorelease2()
    __attribute__((noinline,used)) id objc_object::rootAutorelease2() {
        ASSERT(!isTaggedPointer());
        // 传入调用 autorelease 的对象
        return AutoreleasePoolPage::autorelease((id)this); 
    }
    
    autorelease() ☆
    // AutoreleasePoolPage 的方法; 入参为 autoreleased 对象
    static inline id autorelease(id obj) {
        ASSERT(obj);
        ASSERT(!obj->isTaggedPointer());
        // 调用 autoreleaseFast, 将 obj 压入page
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }
    
    POOL_BOUNDARY

    push()时:

    autoreleasePool 初始化, 调用 objc_autoreleasePoolPush() ;

    每次push都会创建一个新的 pool, 在 AutoreleasePoolPage 中的 next 指针指向的地址插入一个 边界对象, 然后返回这个边界对象的地址, 用于pop()时传入 ;

    将 next 指针重新指向栈顶下一个为空的内存地址; (此时应为边界对象上面的地址) ;

    pop()时:

    autoreleasePool 析构, 调用 objc_autoreleasePoolPop() ;

    会销毁当前的 pool, 会向pool中晚于传入的边界对象插入的所有对象都发送一次 release 消息, 直到传入的边界对象; 从最后插入到pool的对象一直向前清理, 可以向前跨越若干个page, 直到边界对象所在的page;

    将 next 指针重新指向栈顶下一个为空的内存地址; (此时应为被释放的边界对象之前所处的地址)

    autorelease()时:

    在 AutoreleasePoolPage 中的 next指针指向的地址插入 需要加入 autoreleasepool 的对象;

    将next指针重新指向栈顶下一个为空的内存地址; (此时应为刚刚入栈对象上面的地址)

    总结

    自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage

    调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的

    相关文章

      网友评论

        本文标题:iOS - AutoreleasePool - 底层篇

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