- 自动释放池的主要底层数据结构是:
__AtAutoreleasePool
、AutoreleasePoolPage
- 调用了
autorelease
的对象最终都是通过AutoreleasePoolPage
对象来管理的 是和线程一一对应的
@autoreleasepool {
//...
}
// 编译器会将上面代码改写为一下代码
void *ctx = objc_autoreleasePoolPush();
//{}中的代码
objc_autoreleasePoolPop(ctx)
源码分析
clang重写@autoreleasepool
- objc4源码:NSObject.mm
AutoreleasePoolPage的结构
- 每个
AutoreleasePoolPage
对象占用4096
字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease
对象的地址 - 所有的
AutoreleasePoolPage
对象通过以栈为结点,双向链表的形式连接在一起
- 调用
push
方法会将一个POOL_BOUNDARY
入栈,并且返回其存放的内存地址 - 调用
pop
方法时传入一个POOL_BOUNDARY
的内存地址,会从最后一个入栈的对象开始发送release
消息,直到遇到这个POOL_BOUNDARY
id *next
指向了下一个能存放autorelease
对象地址的区域
Runloop和Autorelease
App启动后,iOS在主线程的Runloop
中注册了2个Observer
管理和维护AutoreleasePool
,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()
,打印currentRunLoop
可以看到。
<CFRunLoopObserver 0x6080001246a0 [0x101f81df0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1020e07ce), context = <CFArray 0x60800004cae0 [0x101f81df0]>{type = mutable-small, count = 0, values = ()}}
<CFRunLoopObserver 0x608000124420 [0x101f81df0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1020e07ce), context = <CFArray 0x60800004cae0 [0x101f81df0]>{type = mutable-small, count = 0, values = ()}}
- 第1个
Observer
监听了kCFRunLoopEntry
事件,会调用objc_autoreleasePoolPush()
创建自动释放池,其 order 是-2147483647
,优先级最高,保证创建释放池发生在其他所有回调之前。 - 第二个
Observer
监视了两个事件,这个 Observer 的 order 是2147483647
,优先级最低,保证其释放池发生在其他所有回调之后- 监听了
kCFRunLoopBeforeWaiting
(准备进入休眠)时,会调用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
释放旧的池并创建新池; - 监听了
kCFRunLoopBeforeExit
事件,会调用objc_autoreleasePoolPop()
- 监听了
主线程的其他操作通常均在这个AutoreleasePool之内(main函数中),以尽可能减少内存维护操作(当然你如果需要显式释放【例如循环】时可以自己创建AutoreleasePool否则一般不需要自己创建)。
常见面试题:
1. 自动释放池是什么时候创建的?什么时候销毁的?
- 创建,运行循环检测到事件并启动后,就会创建自动释放池
- 销毁:一次完整的运行循环结束之前,会被销毁
2. 以上代码是否有问题?如果有,如何解决?
for (long i = 0; i < largeNumber; ++i) {
NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
str = [str uppercaseString];
str = [str stringByAppendingString:@" - world"];
}
解决方法:引入自动释放池
- 1> 外面加自动释放池(快?):能够保证for循环结束后,内部产生的自动释放对象,都会被销毁,需要等到 for 结束后,才会释放内存
- 2> 里面加自动释放池(慢?):能够每一次 for 都释放产生的自动释放对象!
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"start");
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
[self answer1];
NSLog(@"外 %f", CFAbsoluteTimeGetCurrent() - start);
start = CFAbsoluteTimeGetCurrent();
[self answer2];
NSLog(@"内 %f", CFAbsoluteTimeGetCurrent() - start);
}
- (void)answer1 {
@autoreleasepool {
for (long i = 0; i < largeNumber; ++i) {
NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
str = [str uppercaseString];
str = [str stringByAppendingString:@" - world"];
}
}
}
- (void)answer2 {
for (long i = 0; i < largeNumber; ++i) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
str = [str uppercaseString];
str = [str stringByAppendingString:@" - world"];
}
}
}
- 实际测试结果,是运行循环放在内部的速度更快!
- 日常开发中,如果遇到局部代码内存峰值很高,可以引入自动释放池及时释放延迟释放对象