美文网首页
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