美文网首页
iOS-- autoreleasePool(自动释放池)

iOS-- autoreleasePool(自动释放池)

作者: Engandend | 来源:发表于2020-09-10 10:23 被阅读0次

    手动目录

    • 初探autoreleasePool结构
    • AutoreleasePoolPage 结构
    • 每个Page能装多少个对象
    • push & pop
    • 面试题

    初探autoreleasePool结构

    用clang的方式探索autoreleasePool

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

    用clang命令:clang -rewrite-objc main.m 之后得到.cpp文件

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

    关键就在{ __AtAutoreleasePool __autoreleasepool; } ,在.cpp 文件中 找到其结构

    // 是一个结构体
    struct __AtAutoreleasePool {
      __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}      // 构造函数
      ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}         // 析构函数
      void * atautoreleasepoolobj;
    };
    

    在这个结构体中,它调用了一个objc_autoreleasePoolPush方法 ,那么它到底干了什么,我们在objc源码中去看

    objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();          // 压栈
    }
    
    class AutoreleasePoolPage : private AutoreleasePoolPageData
    

    AutoreleasePoolPage 结构

    找到这里的时候,我们先来看看AutoreleasePoolPage的说明

       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. 
    

    说明里面提到几点:
    1、stack 先进后出
    2、pool boundary 边界(也有叫哨兵的),它是一个对象,可以理解为标记。
    3、释放池里存的 要么是边界,要么是需要释放的对象。
    4、是一个双向链表结构

    继续跟踪AutoreleasePoolPage的父类AutoreleasePoolPageData

    class AutoreleasePoolPage;
    struct AutoreleasePoolPageData
    {
        magic_t const magic;                        // 检查校验完整性的变量                   16位
        __unsafe_unretained id *next;               // 指向新加入的autorelease对象下一个节点   8
        pthread_t const thread;                     // 当前所在的线程                        8
        AutoreleasePoolPage * const parent;         // 父节点 指向前一个page                  8
        AutoreleasePoolPage *child;                 // 子节点 指向下一个page                 8
        uint32_t const depth;                       // 链表的深度,节点个数                   4
        uint32_t hiwat;                             // 数据容纳的一个上限                     4
    
        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)
        {
        }
    };
    
    struct magic_t {
        static const uint32_t M0 = 0xA1A1A1A1;              // 全局静态 在常量区 .data段  不占这里的位数
    #   define M1 "AUTORELEASE!"
        static const size_t M1_len = 12;                    // 全局静态 在常量区 .data段  不占这里的位数
        uint32_t m[4];                                      // 4 * 4 = 16   这才是为什么这个结构体16位
        ......
    };
    

    相关属性说明

    • magic 检查校验完整性的变量
    • next 指向新加入的autorelease对象下一个位置
    • thread page当前所在的线程
    • parent 父节点 指向前一个page
    • child 子节点 指向下一个page
    • depth 链表的深度,节点个数
    • hiwat 数据容纳的一个上限

    链表的结构大概为:


    AutoreleasePoolPage

    每个Page能装多少个对象

    我们通过源码来找:

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

    begin 从 sizeof(this))开始 (上面计算了它的大小 56) 到 SIZE结束
    SIZE = PAGE_MIN_SIZE = PAGE_SIZE = 4096;
    那么能存的对象数量是:
    (4096 - 56)/8 = 505*

    打印来验证
    在可运行的objc源码中, 修改setting -> automatic reference counting = NO

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            for (NSInteger i = 0; i < 5; i ++) {
                NSObject *obj = [[NSObject alloc] autorelease];
            }
            _objc_autoreleasePoolPrint();
            return 0;
        }
    }
    
    // 打印结果
    1objc[65796]: ##############
    objc[65796]: AUTORELEASE POOLS for thread 0x1000d5dc0
    objc[65796]: 6 releases pending.                                      // 这里说了  添加了6个
    objc[65796]: [0x10181d000]  ................  PAGE  (hot) (cold)
    objc[65796]: [0x10181d038]  ################  POOL 0x10181d038
    objc[65796]: [0x10181d040]       0x10103d480  NSObject
    ....
    objc[65796]: ##############
    
    // 上面的循环换成 1 + 504 
    1objc[66085]: ##############
    objc[66085]: AUTORELEASE POOLS for thread 0x1000d5dc0
    objc[66085]: 506 releases pending.                                                // 这里说了 506
    objc[66085]: [0x101809000]  ................  PAGE (full)  (cold)      // 注意这里:  page  full(满了)
    objc[66085]: [0x101809038]  ################  POOL 0x101809038
    objc[66085]: [0x101809040]       0x10104af30  NSObject
    ....
    objc[66085]: [0x101809ff8]       0x10104f6e0  NSObject
    objc[66085]: [0x10180b000]  ................  PAGE  (hot)               // 这里也换了一页
    objc[66085]: [0x10180b038]       0x10104f6f0  NSObject
    objc[66085]: ##############
    KCObjcTest was compiled with optimization - stepping may behave oddly; variables may not be available.
    

    通过更多的打印,我们发现

    每页可以添加505个对象
    但是第一页实际只加进去504,因为第一页有一个边界对象
    后面的每页可以实际加入505个对象

    push & pop

    先看源码 如何将对象加入进去
    在看源码之前先了解hotPage是当前页 对看源码有帮助

    void *
    objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    
    // push
    static inline void *push() 
        {
            id *dest;
            if (slowpath(DebugPoolAllocation)) {
                // Each autorelease pool starts on a new pool page.
                dest = autoreleaseNewPage(POOL_BOUNDARY);
            } else {
                dest = autoreleaseFast(POOL_BOUNDARY);
            }
            ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
            return dest;
        }
    
    //autoreleaseFast
    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);
            }
        }
    
    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);
            }
        }
    
    后面的 add()  、autoreleaseFullPage()、autoreleaseNoPage()可以继续往下跟。代码太长,就不贴出来了
    

    pop

    objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }
    
    pop(void *token) {}
    
    popPage()
    
    // 具体代码就不贴了。
    

    压栈过程 (push)
    1、如果有分页,并且这个页还没有满,直接add
    2、如果有分页,并且满了,创建一张新页便加入对象。
    3、如果还没有页,创建一个page 并加入边界对象和要加入的对象。

    出栈(pop)
    1、通过while循环,从后往前找到所有page的对象,并进行release操作
    2、删除所有的空表

    autoreleasePool 嵌套

    嵌套使用autoreleasePool的情况下,会发生什么?
    我们用代码来打印

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            NSObject *obj = [[NSObject alloc] autorelease];
            @autoreleasepool {
                NSObject *obj = [[NSObject alloc] autorelease];
                @autoreleasepool {
                    NSObject *obj = [[NSObject alloc] autorelease];
                    _objc_autoreleasePoolPrint();
                }
            }
        }
        return 0;
    }
    // 打印结果
    objc[70971]: ##############
    objc[70971]: AUTORELEASE POOLS for thread 0x1000d5dc0
    objc[70971]: 6 releases pending.
    objc[70971]: [0x10080c000]  ................  PAGE  (hot) (cold)
    objc[70971]: [0x10080c038]  ################  POOL 0x10080c038
    objc[70971]: [0x10080c040]       0x100683ca0  NSObject
    objc[70971]: [0x10080c048]  ################  POOL 0x10080c048
    objc[70971]: [0x10080c050]       0x100683e40  NSObject
    objc[70971]: [0x10080c058]  ################  POOL 0x10080c058
    objc[70971]: [0x10080c060]       0x100683b90  NSObject
    objc[70971]: ##############
    

    在看另外一种打印

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            NSObject *objc = [[NSObject alloc] autorelease];
            NSLog(@"--------objc = %@",objc);
            dispatch_async(dispatch_get_global_queue(0, 0), ^{        // 子线程有一个隐性的autoreleasepool
                @autoreleasepool {
                    sleep(1);
                    NSObject *obj = [[NSObject alloc] autorelease];
                    NSLog(@"+++++++obj = %@",obj);
                    _objc_autoreleasePoolPrint();
                }
            });
            _objc_autoreleasePoolPrint();
        }
        sleep(2);          // 防止子线程没有执行完
        return 0;
    }
    
    // 打印结果
    Test[74472:1192459] --------objc = <NSObject: 0x10290d0b0>
    objc[74472]: ##############                                     // 第一个autoleasepool
    objc[74472]: AUTORELEASE POOLS for thread 0x1000d5dc0                  // 依赖的线程不同
    objc[74472]: 2 releases pending.
    objc[74472]: [0x10300a000]  ................  PAGE  (hot) (cold)
    objc[74472]: [0x10300a038]  ################  POOL 0x10300a038
    objc[74472]: [0x10300a040]       0x10290d0b0  NSObject
    objc[74472]: ##############
    KCObjcTest[74472:1192759] +++++++obj = <NSObject: 0x102804ac0>
    objc[74472]: ##############                  // 第二个autoleasepool
    objc[74472]: AUTORELEASE POOLS for thread 0x70000b669000
    objc[74472]: 3 releases pending.
    objc[74472]: [0x10380c000]  ................  PAGE  (hot) (cold)
    objc[74472]: [0x10380c038]  ################  POOL 0x10380c038        // 一个隐性autoreleasepool
    objc[74472]: [0x10380c040]  ################  POOL 0x10380c040        //手动添加的autoreleasepool
    objc[74472]: [0x10380c048]       0x102804ac0  NSObject
    objc[74472]: ##############
    
    • 1、相同的线程,嵌套autoreleasePool 用的是相同的page,但是每个autoreleasepool 都会添加一个边界对象(哨兵)
    • 2、 不同的线程 使用不同的autoreleasePool

    面试题

    来源

    • 1、ARC 下什么样的对象由 Autoreleasepool 管理
      当使用alloc/new/copy/mutableCopy开始的方法进行初始化时,会生成并持有对象(也就是不需要pool管理,系统会自动的帮他在合适位置release)

      不是通过alloc等出来的对象 会加入自动释放池 比如id obj = [NSMutableArray array];

    • 2、Autoreleasepool 与 Runloop 的关系
      主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理

    • 3、子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?

      在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,

    相关文章

      网友评论

          本文标题:iOS-- autoreleasePool(自动释放池)

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