自动释放池
●自动释放池的主要底层数据结构是:__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否则一般不需要自己创建)。
常见面试题:
- 自动释放池是什么时候创建的?什么时候销毁的?
●创建,运行循环检测到事件并启动后,就会创建自动释放池
●销毁:一次完整的运行循环结束之前,会被销毁
- 以上代码是否有问题?如果有,如何解决?
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"];
}
}
}
●实际测试结果,是运行循环放在内部的速度更快!
●日常开发中,如果遇到局部代码内存峰值很高,可以引入自动释放池及时释放延迟释放对象