autoreleasepool 是如何实现的
查看下 @autoreleasepool
的 cpp 代码实现:
1 | /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; |
查看 __AtAutoreleasePool
的结构,发现是一个结构体,在构造方法中调用了 objc_autoreleasePoolPush()
,并在析构中调用了 objc_autoreleasePoolPop()
。
1 | struct __AtAutoreleasePool { |
相当于源码被重写为:
1 | { |
AutoreleasePoolPage 的结构
objc_autoreleasePoolPush()
和 objc_autoreleasePoolPop()
内部分别调用的是 AutoreleasePoolPage
的 push()
和 pop()
方法。AutoreleasePoolPage
是 C++ 实现的一个类。
1 | class AutoreleasePoolPage { |
从数据格式上可以看出 AutoreleasePoolPage
具有以下特点:
- 本质上是一个双向链表的节点,其中
parent
和child
分别为上个节点和下个节点。 - 重写了
new
运算符,对象会占用 4096 bytes - 本身的属性只用了 56 个bytes,剩下的空间用来存放加入自动释放池的对象
AutoreleasePoolPage 的加入和移除对象
objc_autoreleasePoolPush()
函数最终会调用 autoreleaseFast()
函数,在缓存释放池中插入 POOL_BOUNDARY
,这是一个宏,其实是一个 nil
。
在 ARC 下调用 autorelease
方法会调用 autoreleaseFast()
函数自动把对象加入自动释放池。
1 | @autoreleasepool { // atautoreleasepoolobj = objc_autoreleasePoolPush(); |
上述代码会依次在链表中加入 POOL_BOUNDARY
、obj1
、obj2
。
当 调用 objc_autoreleasePoolPop(atautoreleasepoolobj);
时,就会把地址为 atautoreleasepoolobj
的 POOL_BOUNDARY
之后的所有对象都发送一遍 release
消息。并将 POOL_BOUNDARY
及其之后的对象给移除。
autoreleaseFast 的实现
autoreleaseFast
函数会先获取当前的 AutoreleasePoolPage,当前的 page 被称为 hotPage
。
- 获取 hotPage
- 如果 hotPage 存在,且为用满,则将对象加入当前 page
- 如果 hotPage 存在,但是满了,则创建下一个节点,将下一个节点设置为 hotPage,并将对象加入 hotPage
- 如果 hotPage 不存在,则创建一个节点,将该节点设置为 hotPage,并将对象加入 hotPage
hotPage 的存取
1 |
|
hotPage 是存放在当前线程的私有存储中,所以是线程安全的。
autorelease 和 runloop
先思考一个问题:AutoreleasePoolPage
的作用是把一个对象加入自动释放池后,在 objc_autoreleasePoolPop
的时候就会自动清理和释放这些对象,而我们的APP一经启动,在运行的这段时间内,是如何进行清理的,即什么时候会调用 objc_autoreleasePoolPop
方法?
答案就是主线程在 runloop 中添加了两个 observe,分别监听了 kCFRunLoopEntry
和 kCFRunLoopBeforeWaiting
,来做objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
操作。
总结
autoreleasepool
的实现是一个双向链表,当 push 的时候会插入一个 nil 来当做哨兵对象,并把这个哨兵对象的地址返回,之后任何对象调用 autorelease
方法时,都会把对象插入进去。当调用 pop 方法时,入参就是哨兵对象的地址,autoreleasepool
会把入参地址之后的所有对象都挨个发送 release
消息。