一,@autoReleasePool{}
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
我们平时创建一个main函数的代码的时候,就会发现其中有一个这个东西@autoreleasepool{},使用clang编译之后:@autoreleasepool{…}被编译成了{__AtAutoreleasePool
__autoreleasepool
; … }。
__AtAutoreleasePool到底是什么?
struct __AtAutoreleasePool {
// 构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
// 析构函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
它其实是一个结构体,在创建__AtAutoreleasePool
结构体变量的时候调用了objc_autoreleasePoolPush(void)
,销毁的时候会调动objc_autoreleasePoolPop(void *)
,分别是构造函数和析构函数,所以我们可以看出其其实是一个C++封装的自动释放池变量,会将@autoreleasepool{…}中{}中的内容添加到自动释放池中,方便内存管理。
二,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构成的双向链表。
- 每个AutoreleasePoolPage对象占用4096字节内存,其中56个字节用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址。
- 调用push()方法,往自动释放池中添加一个
POOL_BOUNDARY
,并返回它存放的内存地址;接着每有一个对象调用autorelease
方法,会将它的内存地址添加进自动释放池中。 - 会调用
pop
方法的传参token
即为POOL_BOUNDARY
对应在Page中的地址。当销毁自动释放池时,会调用pop()
方法将自动释放池中的autorelease
对象全部释放(实际上是从自动释放池的中的最后一个入栈的autorelease
对象开始,依次给它们发送一条release
消息,直到遇到这个POOL_BOUNDARY
)
讲一下POOL_BOUNDARY
POOL_BOUNDARY
的前世叫做POOL_SENTINEL,称为哨兵对象或者边界对象;POOL_BOUNDARY
用来区分不同的自动释放池,以解决自动释放池嵌套的问题;
Autoreleasepool嵌套探究
单个page嵌套(autorelease对象较少)
由于ARC
环境下不能调用autorelease
等方法,所以需要将工程切换为MRC
环境。
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool { //r1 = push()
_objc_autoreleasePoolPrint(); // print2
NSObject *p1 = [[[NSObject alloc] init] autorelease];
NSObject *p2 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
@autoreleasepool { //r2 = push()
NSObject *p3 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print4
@autoreleasepool { //r3 = push()
NSObject *p4 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print5
} //pop(r3)
_objc_autoreleasePoolPrint(); // print6
} //pop(r2)
_objc_autoreleasePoolPrint(); // print7
} //pop(r1)
_objc_autoreleasePoolPrint(); // print8
return 0;
}
autorelease对象的进出栈流程图如下所示。它们的作用域只在@autoreleasepool{}之间,超出之后就全部调用pop释放。
印证了上面的push和pop方法和POOL_BOUNDARY的原理
多个page嵌套(autorelease对象较少)
int main(int argc, const char * argv[]) {
@autoreleasepool { //r1 = push()
for (int i = 0; i < 600; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
@autoreleasepool { //r2 = push()
for (int i = 0; i < 500; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
@autoreleasepool { //r3 = push()
for (int i = 0; i < 200; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
} //pop(r3)
} //pop(r2)
} //pop(r1)
return 0;
}
一个AutoreleasePoolPage对象的内存大小为4096个字节,它自身成员变量占用内存56个字节,所以剩下的4040个字节用来存储autorelease对象的内存地址。又因为64bit下一个OC对象的指针所占内存为8个字节,所以一个Page可以存放505个对象的地址。
当一个page满的时候,会创建一个新的page,并且每个page之间是以栈为结点通过双向链表的形式组合而成。
autorelease和RunLoop
iOS在主线程RunLoop中注册了两个Observer
- 第1个Observer监听了
kCFRunLoopEntry
事件,在即将进入RunLoop时,会调用objc_autoreleasePoolPush()
,自动创建一个__AtAutorreleasePool结构体对象。 - 第2个Observer
- ① 监听了
kCFRunLoopBeforeWaiting
事件,RunLoop即将休眠时,会调用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
;释放旧的池,创建新的池。 - ② 监听了
kCFRunLoopBeforeExit
事件,RunLoop即将退出时,会调用objc_autoreleasePoolPop()
释放自动释放池。
- ① 监听了
总结:
- 自动释放池本质是一个
AutoReleasePoolPag
e结构体对象,是一个栈结构存储的页,每一个AutoReleasePoolPage
都是一个双向链表的形式连接。 - 自动释放池的压栈和出栈主要是通过
__AtAutoreleasePool
的构造函数和析构函数objc_autoreleasePoolPush
和objc_autoreleasePoolPop
- 调用push操作就是创建一个新的
AutoreleasePoolPage
,对AutoreleasePoolPage
的具体操作就是插入一个哨兵,并返回插入哨兵POOL_BOUNDARY
的内存地址,每有对象调用autorelease
方法,会将对象的内存地址添加进page中。 - 调用
pop
操作的时候,传入的参数就是push
返回的哨兵POOL_BOUNDARY
的内存地址token
,从自动释放池最后push
的autorelease
对象开始依次release
释放掉,直到遇到哨兵POOL_BOUNDARY
。