查看下 @autoreleasepool 的 cpp 代码实现:

1
2
3
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
code ...
}

查看 __AtAutoreleasePool 的结构,发现是一个结构体,在构造方法中调用了 objc_autoreleasePoolPush(),并在析构中调用了 objc_autoreleasePoolPop()

1
2
3
4
5
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

相当于源码被重写为:

1
2
3
4
5
{
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
// code ...
objc_autoreleasePoolPop(atautoreleasepoolobj);
}

AutoreleasePoolPage 的结构

objc_autoreleasePoolPush()objc_autoreleasePoolPop() 内部分别调用的是 AutoreleasePoolPagepush()pop() 方法。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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class AutoreleasePoolPage {
magic_t const magic; // 标识,是否是一个 AutoreleasePoolPage 对象,16 bytes
id *next; // 下一个存放对象的地址,8 bytes
pthread_t const thread; // 所属线程,8 bytes
AutoreleasePoolPage * const parent; // 双向链表的上一个节点,8 bytes
AutoreleasePoolPage *child; // 双向链表的下一个节点,8 bytes
uint32_t const depth; // 链表深度,4 bytes
uint32_t hiwat; // 4 bytes

// SIZE-sizeof(*this) bytes of contents follow

static void * operator new(size_t size) {
return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
static void operator delete(void * p) {
return free(p);
}

id * begin() { // 当前 page 存放的第一个对象
return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() { // 当前 page 存放的最后一个对象
return (id *) ((uint8_t *)this+SIZE);
}

bool empty() { // 是否是空的 page
return next == begin();
}

bool full() { // 是否是满的地址
return next == end();
}

bool lessThanHalfFull() { // 存放对象是否小于容器的一半容量
return (next - begin() < (end() - begin()) / 2);
}

id *add(id obj) // 新增一个存放对象
{
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
return ret;
}

void releaseUntil(id *stop)
{
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
while (page->empty()) {
page = page->parent;
setHotPage(page);
}

id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));

if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}

setHotPage(this);
}
}

从数据格式上可以看出 AutoreleasePoolPage 具有以下特点:

  1. 本质上是一个双向链表的节点,其中 parentchild 分别为上个节点和下个节点。
  2. 重写了 new 运算符,对象会占用 4096 bytes
  3. 本身的属性只用了 56 个bytes,剩下的空间用来存放加入自动释放池的对象

autoreleasepool.page

AutoreleasePoolPage 的加入和移除对象

objc_autoreleasePoolPush() 函数最终会调用 autoreleaseFast() 函数,在缓存释放池中插入 POOL_BOUNDARY,这是一个宏,其实是一个 nil

在 ARC 下调用 autorelease 方法会调用 autoreleaseFast() 函数自动把对象加入自动释放池。

1
2
3
4
@autoreleasepool { // atautoreleasepoolobj = objc_autoreleasePoolPush();
id obj1 = [[[NSMutableDictionary alloc] init] autorelease];
id obj2 = [[[NSMutableDictionary alloc] init] autorelease];
} // objc_autoreleasePoolPop(atautoreleasepoolobj);

上述代码会依次在链表中加入 POOL_BOUNDARYobj1obj2

autoreleasepool.002

当 调用 objc_autoreleasePoolPop(atautoreleasepoolobj); 时,就会把地址为 atautoreleasepoolobjPOOL_BOUNDARY 之后的所有对象都发送一遍 release 消息。并将 POOL_BOUNDARY 及其之后的对象给移除。

autoreleaseFast 的实现

autoreleaseFast 函数会先获取当前的 AutoreleasePoolPage,当前的 page 被称为 hotPage

  1. 获取 hotPage
  2. 如果 hotPage 存在,且为用满,则将对象加入当前 page
  3. 如果 hotPage 存在,但是满了,则创建下一个节点,将下一个节点设置为 hotPage,并将对象加入 hotPage
  4. 如果 hotPage 不存在,则创建一个节点,将该节点设置为 hotPage,并将对象加入 hotPage

hotPage 的存取

1
2
3
4
5
6
7
8
9
10
11
12
13
#define key 43
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}

hotPage 是存放在当前线程的私有存储中,所以是线程安全的。

autorelease 和 runloop

先思考一个问题:AutoreleasePoolPage 的作用是把一个对象加入自动释放池后,在 objc_autoreleasePoolPop 的时候就会自动清理和释放这些对象,而我们的APP一经启动,在运行的这段时间内,是如何进行清理的,即什么时候会调用 objc_autoreleasePoolPop 方法?

答案就是主线程在 runloop 中添加了两个 observe,分别监听了 kCFRunLoopEntrykCFRunLoopBeforeWaiting,来做
objc_autoreleasePoolPushobjc_autoreleasePoolPop 操作。

总结

autoreleasepool 的实现是一个双向链表,当 push 的时候会插入一个 nil 来当做哨兵对象,并把这个哨兵对象的地址返回,之后任何对象调用 autorelease 方法时,都会把对象插入进去。当调用 pop 方法时,入参就是哨兵对象的地址,autoreleasepool 会把入参地址之后的所有对象都挨个发送 release 消息。