AutoRelease Pool 总结

2018/7/26 posted in  iOS

主要iOS的研发生涯是使用Swift作为主力语言进行实现的,在项目中也基本没有遇到手动使用autorelease的情况。但是因为这是iOS相关的底层一点的知识,因此书写此文章,作为学习的记录。如果文中有疏漏和错误,也欢迎大家指正。

基本知识点

Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed.

我们可以知道每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候自动创建

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

在主线程的 NSRunLoop 的每个 event loop 开始前,系统会自动创建一个 自动释放池 ,并在 event loop 结束时 drain掉 当前的自动释放池,因而在处理完时间之后,会对池中的对象进行释放操作

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.

每个线程都会维护自己的 自动释放池的栈

同一个对象可以多次加入自动释放池

在自动释放池要销毁时,会向池内的所有对象发送release消息

使用方式

  1. 在MRC的模式下,需要手动创建一个自动释放池。 对于需要加入池中进行管理的对象,调用 [obj autorelease]
  2. 在ARC的模式下

    //OC 代码
    @autorelease{   
      //code
    }
    
    //swift 代码
    
    autorelease {
       //code
    }
    
    

原理

每一个线程都会持有一个管理自动释放池的栈,对于该线程中每一个新创建的自动释放池,都会自动加入到栈顶。

对于OC中的代码

@autorelease {
//代码
}

在经过编译器进行处理后,会转换成以下的代码:

void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
//context 指的是哨兵

实际autoreleasepool的创建之后调用了 AutoreleasePoolPage的方发

我们来看一下AutoReleasePoolPage这个C++实现的代码底做了些什么

AutoreleasePoolPage 是一个4k大小的空间,其基本结构是

//AutoreleasePoolPage
magic_t const magic; //用于校验AutoreleasePoolPage的结构是否完整
id *next; //指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
pthread_t const thread;  //指向当前指针
AutoreleasePoolPage *const parent; //指向父节点 因此 第一个节点的parent 值为nil
AutoreleasePoolPage *child //指向子节点,最后一个节点的child 值为nil
uint32_t const depth; // 表示深度,从0开始,往后递增1
unit32_t hiwat;

从上方AutoreleasePoolPage的结构代码可以看出,它同时拥有指向父节点和子节点的指针,也就是说它可以用于组成双向链表。
而在应用中,AutoreleasePoolPage它4K大小的空间除了保存上面的变量之外,其余的控件都用于保存autorelease的对象指针。当next==begin()时,表示AutoreleasePoolPage为空;当next==end()时,表示AutoreleasePoolPage当前已满,需要添加一个新的AutoreleasePoolPage。并将已满的AutoreleasePoolPage.child指针指向新的page。这样就实现了自动开辟空间来存储autorelease的对象。

当创建一个新的autorelease Pool时,管理autorelease Pool的栈在栈顶添加了新的autorelease Pool时,对应的当前AutorelesePoolPage中的next指向的位置会添加一个哨兵,用于标识此处添加的Autorelease pool。next指针后移, 池中新的autorelease的对象会一次存放在next指针指向的地方。

当需要销毁一个autorelease Pool时,会在AutorelesePoolPage寻找到对应的哨兵的位置,对其向后的所有对象以及以后的AutorelesePoolPage都会执行release操作。

从本质上来说,通过对AutorelesePoolPage的操作,完成了线程中autorelease pool对于添加到池中对象的管理。

并且上面提到的,每个线程中都有一个栈,用于管理autorelease pool,我认为这也是通过 AutorelesePoolPage的双向链表实现的。

使用场景

  • 你编写是命令行工具的代码,而不是基于 UI 框架的代码
  • 你需要写一个循环,里面会创建很多临时的对象
  • 这时候你可以在循环内部的代码块里使用一个 @autoreleasepool {},这样这些对象就能在一次迭代完成后被释放掉。这种方式可以降低内存最大占用
  • 当你大量使用辅助线程
  • 你需要在线程的任务代码中创建自己的 @autoreleasepool {}

疑问

文章有一段描述

MRC下需要对象调用autorelease才会入池, ARC下可以通过__autoreleasing修饰符, 否则的话看方法名, 非alloc/new/copy/mutableCopy开头的方法编译器都会自动帮我们调用autorelease方法.

参考文章

1.Sunny的这篇文章讲的很详细
2.https://blog.ibireme.com/2015/05/18/runloop/
3.https://imtangqi.com/2016/04/15/autorelease-pool-in-ios/