iOS底层原理 - 内存管理 之 autorelease
面试题引发的思考:
Q: 自动释放池autoreleasepool底层原理介绍:
- 自动释放池的主要底层数据结构是:
__AtAutoreleasePool、AutoreleasePoolPage; - 调用了
autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的。
- 调用
push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址; - 调用
autorelease方法会将对象地址值入栈; - 调用
pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY; id *next指向了下一个能存放autorelease对象地址的区域。
Q: AutoreleasePoolPage为何设置成双向链表结构?
- 因为每个
AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址; - 如果存放地址已满,就会创建新的
AutoreleasePoolPage对象; - 并通过
child指针指向新建的AutoreleasePoolPage对象。
1. autoreleasepool分析
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@end
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__);
[super dealloc];
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[[Person alloc] init] autorelease];
}
return 0;
}
// 打印结果
Demo[1234:567890] -[Person dealloc]
将OC代码转换为C\C++代码,使用xcrun:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
main函数
__AtAutoreleasePool底层结构为:
__AtAutoreleasePool结构体
objc_autoreleasePoolPush、objc_autoreleasePoolPop底层结构为:
objc_autoreleasePoolPush、objc_autoreleasePoolPop
由OC源码可知:
objc_autoreleasePoolPush、objc_autoreleasePoolPop底层分别调用AutoreleasePoolPage的push()方法和pop()方法。
由以上分析可知:
@autoreleasepool {
Person *person = [[[Person alloc] init] autorelease];
}
#pragma mark - 将以上OC代码转换为C\C++代码为:
{
__AtAutoreleasePool __autoreleasepool;
Person *person = [[[Person alloc] init] autorelease];
}
#pragma mark - 进一步转换为C\C++代码为:
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
由此得出结论:
@autoreleasepool {
// 其他代码
}
#pragma mark - 转换为C\C++代码为:
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
// 其他代码
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
由以上步骤总结可知:
- 自动释放池的主要底层数据结构是:
__AtAutoreleasePool、AutoreleasePoolPage;- 调用了
autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的。
2. AutoreleasePoolPage分析
由OC源码可知:
AutoreleasePoolPage主要构成
每个
AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址;所有的
AutoreleasePoolPage对象通过双向链表的形式连接在一起。双向链表是通过
parent指针和child指针联系在一起的:
parent指针指向上一个AutoreleasePoolPage对象的地址值;
child指针指向下一个AutoreleasePoolPage对象的地址值。
AutoreleasePoolPage双向链表结构如下:
AutoreleasePoolPage双向链表结构
begin()、end()
begin():this指针 +this大小,即0x1000 + 7 * 8 = 0x1038end():this指针 +SIZE,即0x1000 + 4096 = 0x2000
3. 方法分析
(1) push方法分析
push方法
由源码分析可知:
调用
push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址。
(2) autorelease方法分析
autorelease方法
由源码分析可知:
调用
autorelease方法会将对象地址值入栈。
(3) pop方法分析
pop方法
由源码分析可知:
调用
pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY。
(4) 总结可知
- 调用
push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址;- 调用
autorelease方法会将对象地址值入栈;- 调用
pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY;id *next指向了下一个能存放autorelease对象地址的区域。
(5) 实例分析:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool { // 位置一:r1 = push()
Person *p1 = [[[Person alloc] init] autorelease];
Person *p2 = [[[Person alloc] init] autorelease];
@autoreleasepool { // 位置二:r2 = push()
Person *p3 = [[[Person alloc] init] autorelease];
@autoreleasepool { // 位置三:r3 = push()
Person *p4 = [[[Person alloc] init] autorelease];
} // 位置四:pop(r3)
} // 位置五:pop(r2)
} // 位置六:pop(r1)
return 0;
}
以上对象在AutoreleasePoolPage的内存结构如下:
AutoreleasePoolPage内存结构
4. 自动释放池查看
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
// TODO: ----------------- main -----------------
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
// 位置一:_objc_autoreleasePoolPrint();
Person *p1 = [[[Person alloc] init] autorelease];
Person *p2 = [[[Person alloc] init] autorelease];
// 位置二:_objc_autoreleasePoolPrint();
@autoreleasepool { // r2 = push()
Person *p3 = [[[Person alloc] init] autorelease];
// 位置三:_objc_autoreleasePoolPrint();
@autoreleasepool { // r3 = push()
Person *p4 = [[[Person alloc] init] autorelease];
// 位置四:_objc_autoreleasePoolPrint();
} // pop(r3)
// 位置五:_objc_autoreleasePoolPrint();
} // pop(r2)
// 位置六:_objc_autoreleasePoolPrint();
} // pop(r1)
// 位置七:_objc_autoreleasePoolPrint();
return 0;
}
语句_objc_autoreleasePoolPrint();在不同位置调用打印结果如下:
位置一打印结果
位置二打印结果
位置三打印结果
位置四打印结果
位置五打印结果
位置六打印结果
位置七打印结果