目录
自动释放池介绍
一、介绍
以下摘自苹果官方文档:
在引用计数(相对于垃圾回收)环境中,自动释放池包含了接受autorelease消息的对象, 当池子倾倒的时候,会对里面的每一个对象发送release消息。
因此对一个对象发送autorelease消息而不是release消息,可以增加对象的生命周期直到它所在的AutoreleasePool被倒掉。
通俗解释:
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。
在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。
二、底层代码
原始代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
执行clang命令后转为c++代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
}
return 0;
}
__AtAutoreleasePool 代码结构
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
三、关键类AutoreleasePoolPage
1、AutoreleasePoolPage对象
调用了autorelease的对象,最终都是通过AutoreleasePoolPage对象来管理的
AutoreleasePoolPage的数据结构如下
每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
magic_t const magic; // 用来校验 AutoreleasePoolPage 的结构是否完整 16字节
__unsafe_unretained id *next; // 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向begin() 8字节
pthread_t const thread; // 指向当前线程 8字节
AutoreleasePoolPage * const parent; // 指向父结点,第一个结点的parent值为nil 8字节
AutoreleasePoolPage *child; // 指向子结点,最后一个结点的child值为nil 8字节
uint32_t const depth; // 代表深度,从0开始,往后递增1 4字节
uint32_t hiwat; // 代表high water mark 最大入栈数量标记 4字节
2、两个重要方法
每当调用objc_autoreleasePoolPush即AutoreleasePoolPage::push()方法时,会将POOL_BOUNDARY放到当前page的栈顶,并且返回这个边界对象。
而在调用objc_autoreleasePoolPop即AutoreleasePoolPage::pop()方法时,又会将边界对象以参数传入,这样自动释放池就会向释放池中对象发送release消息,直至找到第一个边界对象为止。
自动释放池查看
苹果提供了一个调试函数:_objc_autoreleasePoolPrint,可以用来打印自动释放池的创建信息
extern void _objc_autoreleasePoolPrint(void); // 需要用extern修饰才能在外部访问
int main(int argc, const char * argv[]) {
@autoreleasepool {// 此处调用push()方法,返回边界对象1,并记录
NSObject *objc = [[[NSObject alloc] init] autorelease];
NSLog(@"objc:%@",objc);
@autoreleasepool {// 此处调用push方法,返回边界对象2,并记录
NSObject *objc2 = [[[NSObject alloc] init] autorelease];
NSLog(@"objc2:%@",objc2);
_objc_autoreleasePoolPrint();
} // 此处调用pop(边界对象2)
} // 此处调用pop(边界对象1)
return 0;
}
输出结果如下
2021-09-20 21:54:23.661658+0800 KCObjcBuild[16800:319949] objc:<NSObject: 0x101a58ab0>
2021-09-20 21:54:23.663090+0800 KCObjcBuild[16800:319949] objc2:<NSObject: 0x1006282b0>
objc[16800]: ##############
objc[16800]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[16800]: 4 releases pending.
objc[16800]: [0x102016000] ................ PAGE (hot) (cold)
objc[16800]: [0x102016038] ################ POOL 0x102016038
objc[16800]: [0x102016040] 0x101a58ab0 NSObject
objc[16800]: [0x102016048] ################ POOL 0x102016048
objc[16800]: [0x102016050] 0x1006282b0 NSObject
objc[16800]: ##############
// 0x102016038 减去 0x102016000 的大小正好是56个字节
// 第一个哨兵对象:0x102016038边界对象
// 第二个哨兵对象:0x102016048边界对象
autorelease 对象在什么时候释放
一、系统释放
@implementation SSLPerson
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
SSLPerson *person = [[[SSLPerson alloc] init] autorelease];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
@end
运行结果:
-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[SSLPerson dealloc]
-[ViewController viewDidAppear:]
可以看到,调用了autorelease方法的person对象并没有在viewDidLoad方法结束后释放,而是在viewWillAppear方法结束后才释放.
说明在viewWillAppear方法结束的时候,调用了pop()方法释放了person对象,其实这是由RunLoop控制的.
下面来讲解一下RunLoop和@autoreleasepool的关系。
RunLoop 与 @autoreleasepool
kCFRunLoopEntry:
在即将进入RunLoop时,会自动创建一个__AtAutoreleasePool结构体对象,并调用objc_autoreleasePoolPush()函数。
kCFRunLoopBeforeWaiting:
在RunLoop即将休眠时,会自动销毁一个__AtAutoreleasePool对象,调用objc_autoreleasePoolPop()。
然后创建一个新的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPush()。
kCFRunLoopBeforeExit:
在即将退出RunLoop时,会自动销毁最后一个创建的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPop()。
所以,在iOS工程中系统干预释放的autorelease对象的释放时机是由RunLoop控制的,会在当前RunLoop每次循环结束时释放。
以上person对象在viewWillAppear方法结束后释放,说明viewDidLoad和viewWillAppear方法在同一次循环里。
二、手动释放
@implementation SSLPerson
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
SSLPerson *person = [[[SSLPerson alloc] init] autorelease];
}
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
@end
运行结果:
-[SSLPerson dealloc]
-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[ViewController viewDidAppear:]
可以看到,手动添加到指定的@autoreleasepool中的autorelease对象,在@autoreleasepool大括号结束时就会释放了,不受RunLoop控制。
autorelease 对象详解
一、非autorelease对象
使用alloc/new/copy/mutableCopy生成的对象都不是Autorelease
//创建对象,引用计数为1
NSMutableArray *array = [[NSMutableArray alloc] init];
自己生成的对象自己持有,相当于下面的mrc代码
//创建对象
NSMutableArray *array = [[NSMutableArray alloc] init];
//自己持有对象,在arc下这件事编译器帮我们做了
[array retain];
// array的引用计数为1
二、autorelease对象
非alloc/new/copy/mutableCopy生成的对象都是Autorelease
// 引用计数为2
NSMutableArray *array = [NSMutableArray array];
相当于下面的mrc代码
+ (id)array {
id obj = [[NSMutableArray alloc] init];//创建对象
[obj retain];
[obj autorelease];//延迟释放对象(谁创建谁释放)
return obj;
}
NSMutableArray* array = [NSMutableArray array];
[array retain];
// 所以引用计数为2
三、自动释放池应用
- (IBAction)autoreleasepoolUse:(id)sender {
// int num = 10000;
int num = 10;
for (int i = 0; i < num; i++) {
//如果没有使用autoreleasepool内存会暴涨,
//暴涨的原因是img是autorelease对象,它会被放入自动释放池不会立即释放,在runloop休眠的时候才会释放,所以内存在短时间内会暴涨
//使用一个手动创建的autoreleasepool时,离img最近的自动释放池就是我们手动创建的这个
//img会被放入这个自动释放池,在离开作用域的时候会立即释放,所以内存不会暴涨
__weak UIImage *wimg = nil;
@autoreleasepool {
UIImage *img = [UIImage imageNamed:@"XYAppDog"];
NSLog(@"img = %@",img);
// 如果没有 __weak UIImage *wimg = nil; 引用计数是2
// 如果有 __weak UIImage *wimg = nil; 引用计数是3,这个理解不了啊????
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(img)));
wimg = img;
}
NSLog(@"wimg = %@",wimg);// wimg = (null) 也就是说在这里img已经被释放掉了
}
}
行者常至,为者常成!