AutoreleasePoolPage
我们新建一个 Command Line Tool
项目,查看 main.m
文件代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
使用 clang 将 main.m
转化成 C++ 代码
,
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
可以从 main.cpp
中看到如下代码:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
由此可见,ARC下
,我们使用 @autoreleasepool{ }
来使用一个 AutoreleasePool
,随后编译器将其改写成下面的样子:
void *context = objc_autoreleasePoolPush();
// { }中的代码
objc_autoreleasePoolPop(context);
而这两个函数都是对 AutoreleasePoolPage
的简单封装,所以自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
调用了
autorelease的对象
最终都是通过AutoreleasePoolPage
对象来管理的
底层源码
obj4 源码:https://opensource.apple.com/tarballs/objc4/
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
AutoreleasePoolPage
是一个 C++
实现的类
-
AutoreleasePool
并没有单独的结构,而是由若干个AutoreleasePoolPage
以双向链表
的形式组合而成(分别对应结构中的parent指针
和child指针
) -
AutoreleasePool
是按线程一一对应的(结构中的thread指针
指向当前线程) -
AutoreleasePoolPage
每个对象会开辟4096字节
内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间(7 * 8 = 56 字节),剩下的空间(4040字节) 全部用来储存autorelease对象的地址
-
id *next
指针作为游标指向栈顶最新 add 进来的autorelease对象的下一个位置
-
一个AutoreleasePoolPage
的空间被占满时,会新建一个AutoreleasePoolPage对象
,形成双向链表,后面加入的autorelease对象
会添加在新的AutoreleasePoolPage
当中
AutoreleasePoolPage 结构:

-
每当调用一次
objc_autoreleasePoolPush
方法,runtime
会向当前的AutoreleasePoolPage
入栈一个POOL_BOUNDARY
(哨兵对象,值为 nil),并且返回其存放的内存地址 -
autorelease 对象地址
依次入栈到 page 中 -
当调用
objc_autoreleasePoolPop
时,会传入一个POOL_BOUNDARY
(哨兵对象) 的内存地址作为入参- 根据传入的
哨兵对象地址
找到哨兵对象
所处的 page - 在当前page中,将晚于哨兵对象插入的所有
autorelease对象
都发送一次release
消息,并向回移动next指针
到正确位置(从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page)
- 根据传入的
-
嵌套的 AutoreleasePool
也是当调用objc_autoreleasePoolPush
方法时, 会继续向当前的AutoreleasePoolPage
后面入栈一个POOL_BOUNDARY
(哨兵对象)进行标记,并不影响外层的AutoreleasePool
私有API 窥探 AutoreleasePoolPage
存储结构
我们还可以通过 私有API extern void _objc_autoreleasePoolPrint(void);
查看 AutoreleasePoolPage
存储结构;
项目改成
MRC
进行调试:
Objective-C Automatic Refrrence Counting
设置为NO
具体代码如下:
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
WBPerson *p1 = [[[WBPerson alloc] init] autorelease];
WBPerson *p2 = [[[WBPerson alloc] init] autorelease];
@autoreleasepool {
WBPerson *p3 = [[[WBPerson alloc] init] autorelease];
@autoreleasepool {
WBPerson *p4 = [[[WBPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
}
}
return 0;
}
控制台可以看到如下输出:
objc[35517]: ##############
objc[35517]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[35517]: 7 releases pending.
objc[35517]: [0x10080a000] ................ PAGE (hot) (cold)
objc[35517]: [0x10080a038] ################ POOL 0x10080a038
objc[35517]: [0x10080a040] 0x100510bd0 WBPerson
objc[35517]: [0x10080a048] 0x10050fd10 WBPerson
objc[35517]: [0x10080a050] ################ POOL 0x10080a050
objc[35517]: [0x10080a058] 0x10050fe30 WBPerson
objc[35517]: [0x10080a060] ################ POOL 0x10080a060
objc[35517]: [0x10080a068] 0x10050fc00 WBPerson
objc[35517]: ##############
因为一个 AutoreleasePoolPage对象
占用 4096字节内存
,内部成员变量占用 56 字节
(7 * 8 = 56),所以以下三个地址(嵌套三个 @autoreleasepool
)是 POOL_BOUNDARY
(哨兵对象) 的内存地址(一个哨兵对象占2个字节)
objc[35517]: [0x10080a038] ################ POOL 0x10080a038
objc[35517]: [0x10080a050] ################ POOL 0x10080a050
objc[35517]: [0x10080a060] ################ POOL 0x10080a060
参考:
1、小码哥 iOS 底层原理
2、黑幕背后的 Autorelease
网友评论