autorelease

黑幕背后的 Autorelease

autorelease 对象释放时机

在没有手加 Autorelease Pool 的情况下,Autorelease 对象是在当前的 runloop 迭代结束时释放的,而它能够释放的原因是系统在每个 runloop 迭代中都加入了自动释放池 Push 和 Pop

autorelease 原理

AutoreleasePoolPage

ARC 下,我们使用@autoreleasepool{}来使用一个 AutoreleasePool,随后编译器将其改写成下面的样子:

1
2
3
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);

AutoreleasePoolPage 是一个 C++实现的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define PROTECT_AUTORELEASEPOOL 0

class AutoreleasePoolPage
{

#define POOL_SENTINEL 0
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =

// AutoreleasePoolPage 每个对象会开辟4096字节内存
#if PROTECT_AUTORELEASEPOOL
4096; // must be multiple of vm page size
#else
4096; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);

magic_t const magic;
id *next; // 指向栈顶最新add进来的autorelease对象的下一个位置
pthread_t const thread; // 当前线程
AutoreleasePoolPage * const parent; // 双向链表,pre 指针
AutoreleasePoolPage *child; // 双向链表,next 指针
uint32_t const depth;
uint32_t hiwat;

// 其他相关方法
// ...
}

向一个对象发送- autorelease 消息,就是将这个对象加入到当前 AutoreleasePoolPage 的栈顶 next 指针指向的位置

释放

  • objc_autoreleasePoolPush()

每当进行一次 objc_autoreleasePoolPush 调用时,runtime 向当前的 AutoreleasePoolPage 中 add 进一个哨兵对象,值为 0(也就是个 nil)

objc_autoreleasePoolPush 的返回值正是这个哨兵对象的地址

  • objc_autoreleasePoolPop(哨兵对象)
  1. 根据传入的哨兵对象地址找到哨兵对象所处的 page

  2. 在当前 page 中,将晚于哨兵对象插入的所有 autorelease 对象都发送一次 - release 消息,并向回移动 next 指针到正确位置

  3. 补充 2:从最新加入的对象一直向前清理,可以向前跨越若干个 page,直到哨兵所在的 page

嵌套的 AutoreleasePool

pop 的时候总会释放到上次 push 的位置为止,多层的 pool 就是多个哨兵对象而已,就像剥洋葱一样,每次一层,互不影响

作者

Wiley

发布于

2017-06-01

更新于

2024-05-26

许可协议