iOS 底层系列 - AutoreleasePool

1,747 阅读4分钟

一、问题

1. autorelease何时释放

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 RunLoopWithAutoreleasePoolHandler()

第一个 Observer : 监视的事件是 Entry (即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。

第二个 Observer : 监视了两个事件:BeforeWaiting(准备进入休眠) 会调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;

Exit(即将退出Loop) 会调用 _objc_autoreleasePoolPop( ) 来释放自动释放池。

手动干预释放时机

  • 指定autoreleasepool 就是所谓的:当前作用域大括号结束时释放。

系统自动去释放

  • 不手动指定 autoreleasepool,依赖于 main 函数所包的 autoreleasepool, Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。

2. 子线程是否要手动创建autoreleasepool

NSThread 和 NSOperationQueue

  • 开辟子线程需要手动创建 autoreleasepool。

GCD

  • 开辟子线程不需要手动创建 autoreleasepool,因为 GCD的每个队列都会自行创建 autoreleasepool。

3. autorelease 对象在什么情况下会被释放?

分两种情况,手动干预释放 和 系统自动释放。

手动干预释放:

  • 就是指定 autoreleasepool, 当前作用域大括号结束就立即释放。

系统自动去释放:

  • 不手动指定 autoreleasepool, Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。
kCFRunLoopEntry(1): 
第一次进入会自动创建一个autorelease

kCFRunLoopBeforeWaiting(32): 
进入休眠状态前会自动销毁一个autorelease,然后重新创建一个新的autorelease

kCFRunLoopExit(128): 
退出runloop时会自动销毁最后一个创建的autorelease

4. runloop的mode作用是什么?

model 主要是用来指定事件在运行循环中的优先级的,分为:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
  • UITrackingRunLoopMode:ScrollView滑动时
  • UIInitializationRunLoopMode:启动时
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

苹果公开提供的 Mode 有两个:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes)

二、AutoreleasePool

1. 什么是 AutoreleasePool

AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,我们可以给一个对象发送 autorelease 消息,然后当一个runloop 迭代结束时系统才会一次性清理掉被autorelease处理过的对象。

AutoreleasePool 一般用在比如 编写的循环创建了很多临时对象。[UIimage imageWithName:@“”] 避免内存峰值过高。

2. AutoreleasePool 底层结构

AutoreleasePool 底层结构

  • 主要包含 AutoreleasePoolAutoreleasePoolPage, 调用 autorelease 的对象 最终都是通过 AutoreleasePoolPage 进行管理。

**@AutoreleasePool **

  • 会自动转换为 __AtAutoreleasePool 结构体类型,在代码的前后会调用 objc_autoreleasePoolPushobjc_autoreleasePoolPop 方法.

AutoreleasePool

  • 内部是个 双向链表 的结构,每一个都是一个 AutoreleasePoolPage 进行相连,以 child 和 parent 指针进行相连。

push 与 pop 的具体做了什么

objc_autoreleasePoolPush

  • objc_autoreleasePoolPush 会将一个 pool_boundary 压入 poolpage 里面,然后调用 autorelease 的时候,会跟着 pool_boundary 一个一个放入 poolpage 里面。

objc_autoreleasePoolPop

  • objc_autoreleasePoolPop 的 时候会把 边界哨兵对象传递进去,然后从最后一个压入一个 poolpage 的对象,一直调用 release,直到传入的 pool_boundary 哨兵对象。

Runloop 和 Autorelease 释放时机

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 RunLoopWithAutoreleasePoolHandler()

第一个 Observer :

  • 监视的事件是 Entry (即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。

第二个 Observer :

  • 监视了两个事件:BeforeWaiting(准备进入休眠) 会调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;

Exit(即将退出Loop)

  • 会调用 _objc_autoreleasePoolPop( ) 来释放自动释放池。

三、@autoreleasepool 底层

  1. 自动释放池 @autoreleasepool 被转换成 __AtAutoreleasePool 结构体类型。

  2. 实质上是一个__AtAutoreleasePool 的结构体对象。

  3. 只是帮助我们少写了这两行代码而已,然后要根据上述两个方法来分析自动释放池的实现。

int main(int argc, char * argv[]) {
     /* @autoreleasepool */ {

         // 创建自动释放池
         __AtAutoreleasePool __autoreleasepool = objc_autoreleasePoolPush();
        
        // TODO 执行各种操作,将对象加入自动释放池
         
         // 释放自动释放池
         objc_autoreleasePoolPop(__autoreleasepool)
     }
 }

__AtAutoreleasePool 结构体内容

struct __AtAutoreleasePool {

   __AtAutoreleasePool() {
      // objc_autoreleasePoolPush 会调用 AutoreleasePoolPage 的 push 方法
            atautoreleasepoolobj = objc_autoreleasePoolPush();
  }

   ~__AtAutoreleasePool() {
           // objc_autoreleasePoolPop   会调用 AutoreleasePoolPage 的 pop 方法
            objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
 };

底层调用

void *objc_autoreleasePoolPush(void)
{
     return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt)
{
     AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePoolPage

POOL_BOUNDARY 边界对象

define POOL_BOUNDARY nil

objc_autoreleasePoolPush 会把一个哨兵对象放到 POOL——BOUNDARY 里面。 之前的源代码变量名是 POOL_BOUNDARY 边界对象,用来区别每个 page 即每个 AutoreleasePoolPage 边界。