iOS深入理解Autoreleasepool(自动释放池)
一、 @autoreleasepool{}
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
我们平时创建一个main函数的代码的时候,就会发现其中有一个这个东西@autoreleasepool{}
,使用clang编译之后:@autoreleasepool{...}
被编译成了{__AtAutoreleasePool __autoreleasepool; ... }
。
这个__AtAutoreleasePool
到底是什么?
它其实是一个结构体,在创建__AtAutoreleasePool结构体变量的时候调用了objc_autoreleasePoolPush(void),销毁的时候会调动objc_autoreleasePoolPop(void *),其实构造函数和析构函数,所以我们可以看出其其实是一个C++封装的自动释放池变量,会将@autoreleasepool{...}中{}中的内容添加到自动释放池中,方便内存管理。
struct __AtAutoreleasePool {
// 构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
// 析构函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
二、AutoreleasePoolPage
从上边的__AtAutoreleasePool
我们可以看到这两个方法objc_autoreleasePoolPush
和objc_autoreleasePoolPop
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
我们可以看出这里又引入了新的类AutoreleasePoolPage
class AutoreleasePoolPage {
magic_t const magic;//AutoreleasePoolPage 完整性校验
id *next;//存放下一个autorelease对象的地址
pthread_t const thread; //AutoreleasePoolPage 所在的线程
AutoreleasePoolPage * const parent;//父节点
AutoreleasePoolPage *child;//子节点
uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
uint32_t hiwat;
}
每一个自动释放池都是由一系列AutoreleasePoolPage
组成的,并且每一个AutoreleasePoolPage
的大小都是4096
字节(16 进制 0x1000)。
所以我们从上述的源码可以看出,自动释放池其实就是一个由AutoreleasePoolPage
构成的双向链表,其结构中的child
和parent
分别指向其前趋和后继。
其中有 56 bit 用于存储AutoreleasePoolPage
的成员变量,剩下的0x100816038 ~ 0x100817000都是用来存储加入到自动释放池中的对象。
-
调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
-
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
-
id *next指向了下一个能存放autorelease对象地址的区域
三、Runloop和Autorelease
AutoreleasePool创建
- App启动后,苹果在
主线程RunLoop
里注册了两个Observer
,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
。 - 第一个
Observer
监视的事件是Entry(即将进入Loop)
,其回调内会调用_objc_autoreleasePoolPush()
创建自动释放池。其 order 是-2147483647,优先级最高
,保证创建释放池发生在其他所有回调之前。
AutoreleasePool释放
- 第二个
Observer
监视了两个事件:BeforeWaiting(准备进入休眠)
时调用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit(即将退出Loop)
时调用_objc_autoreleasePoolPop()
来释放自动释放池。这个Observer
的 order 是 2147483647,优先级最低
,保证其释放池子发生在其他所有回调之后。 - 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的
AutoreleasePool
环绕着,所以不会出现内存泄漏。
四、面试题AutoreleasePool的原理
-
自动释放池的
本质是一个AutoreleasePoolPage结构体
对象,是一个栈结构
存储的页,每一个AutoreleasePoolPage
都是以双向链表的形式连接
。 -
自动释放池的
压栈
和出栈
主要是通过结构体的构造函数和析构函数调用底层的objc_autoreleasePoolPush
和objc_autoreleasePoolPop
,实际上是调用AutoreleasePoolPage
的push
和pop
两个方法。 -
调用
push操作
其实就是创建一个新的AutoreleasePoolPage
,而AutoreleasePoolPage
的具体操作就是插入一个哨兵POOL_BOUNDARY
,并返回插入哨兵POOL_BOUNDARY
的内存地址。而push
内部调用autoreleaseFast
方法处理,主要有以下三种情况:- a.当
page存在,且不满
时,调用add
方法将对象添加至page
的next
指针处,并将next
指向下一位; - b.当
page存在,且已满
时,调用autoreleaseFullPage
初始化一个新的page
,然后调用add
方法将对象添加至page
栈中; - c.当
page不存在
时,调用autoreleaseNoPage
创建一个hotPage
,然后调用add
方法将对象添加至page
栈中。
- a.当
-
调用
pop操作
时,会传入一个值,这个值就是push
操作的返回值,即哨兵POOL_BOUNDARY
的内存地址token
。所以pop
内部的实现就是根据token
找到哨兵对象
所处的page
中,然后使用objc_release
释放token
之前的对象,并把next
指针到正确位置。
转载自:https://juejin.cn/post/7271188803502080054