深入理解AutoReleasePool总结篇

1,323 阅读4分钟

AutoReleasePool 是什么

1. 自动释放池 ,是OC中的一种内存自动回收机制

2. 它可以延迟加入AutoreleasePool中的变量release的时机。MRC中,调用[obj autorelease],ARC下,@autoreleasepool{}管理

3. 自动释放池是一个个 AutoreleasePoolPage 组成的

 一个page是4096字节大小,

每个 AutoreleasePoolPage 以双向链表连接起来形成一个自动释放池

当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中

初始化调用push 时是把边界对象放到栈顶, 然后对page 中添加的对象等操作

pop 时是传入边界对象,然后对page 中的对象发送release 的消息


创建与释放时机

  • App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
    (添加监听)
  • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。
    其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    (创建+优先级最高)
  • 第二个 Observer 监视了两个事件:
    BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
    Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
    (释放重建+优先级最低)


如何实现

  • 苹果通过声明一个__AtAutoreleasePool类型的局部变量__autoreleasepool
    实现了@autoreleasepool{}。
  • 单个自动释放池的执行过程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)

  • objc_autoreleasePoolPush 和 objc_autoreleasePoolPop 是 AutoreleasePoolPage 对应静态方法 push 和 pop 的封装


结构组成

AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)

  • 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
  • 向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置

  • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)

AutoreleasePoolPage结构

  • AutoreleasePoolPage每个对象会开辟4096字节内存4k(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
  • 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
  • 栈顶的 哨兵对象(POOL_SENTINEL/边界对象)


objc_autoreleasePoolPush

    • Push时都会把边界对象放进栈顶,然后返回边界对象,用于释放。
    • 1. atautoreleasepoolobj = objc_autoreleasePoolPush();
    • 2. AutoreleasePoolPage::push
    • 3. autoreleaseFast(POOL_BOUNDARY)
      • 有 hotPage 并且当前 page 不满,调用 page->add(obj) 方法将对象添加至Page 的栈中
      • 有 hotPage 并且当前 page 已满,调用 autoreleaseFullPage 初始化一个新的页,调用 page->add(obj) 方法将对象添加至Page 的栈中
      • 无 hotPage,调用 autoreleaseNoPage 创建一个 hotPage,调用 page->add(obj) 方法将对象添加至 Page 的栈中
    • 关键代码都是调用autoreleaseFast函数向自动释放池的链表栈中添加一个对象,

objc_autoreleasePoolPop

  • 1. objc_autoreleasePoolPop(atautoreleasepoolobj);
    atautoreleasepoolobj 就是一个 POOL_SENTINEL 自动释放池释放 是 传入 push 返回的边界对象/哨兵对象, Pop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL:(向 pop 方法传入非哨兵参数是可行的,只是我们一般不会传入非哨兵对象。)
  • 2. AutoreleasePoolPage::pop(ctxt);
  • 3. autoreleaseFast(POOL_SENTINEL);
      • 1、使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage pageForPointer 方法主要是通过内存地址的操作,获取当前指针所在页的首地址:
      • 2、调用 releaseUntil 方法释放栈中的对象,直到 stop
      • 3,调用 child 的 kill 方法
      • /2 最后调用的方法 fastCheck() 用来检查当前的 result 是不是一个 AutoreleasePoolPage,releaseUntil 释放对象
      • kill() 方法 当前页面以及子页面全部删除



其他实例:

  • 使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {// 这里被一个局部@autoreleasepool包围着
    }];
    当然,在普通for循环和for in循环中没有,
    所以,还是新版的block版本枚举器更加方便。
    for循环中遍历产生大量autorelease变量时,就需要手加局部AutoreleasePool咯。