• 自动释放池的主要底层数据结构是:__AtAutoreleasePoolAutoreleasePoolPage
  • 调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
  • 是和线程一一对应的

    1. @autoreleasepool {
    2. //...
    3. }
    4. // 编译器会将上面代码改写为一下代码
    5. void *ctx = objc_autoreleasePoolPush();
    6. //{}中的代码
    7. objc_autoreleasePoolPop(ctx)

    源码分析

  • clang重写@autoreleasepool

  • objc4源码:NSObject.mm

image.png

AutoreleasePoolPage的结构

  • 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
  • 所有的AutoreleasePoolPage对象通过以栈为结点,双向链表的形式连接在一起

image.png

  • 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
  • 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
  • id *next指向了下一个能存放autorelease对象地址的区域

Runloop和Autorelease

App启动后,iOS在主线程的Runloop中注册了2个Observer管理和维护AutoreleasePool,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler(),打印currentRunLoop可以看到。

  1. <CFRunLoopObserver 0x6080001246a0 [0x101f81df0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x1020e07ce), context = <CFArray 0x60800004cae0 [0x101f81df0]>{type = mutable-small, count = 0, values = ()}}
  2. <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()

image.png

主线程的其他操作通常均在这个AutoreleasePool之内(main函数中),以尽可能减少内存维护操作(当然你如果需要显式释放【例如循环】时可以自己创建AutoreleasePool否则一般不需要自己创建)。

常见面试题:

1. 自动释放池是什么时候创建的?什么时候销毁的?

  • 创建,运行循环检测到事件并启动后,就会创建自动释放池
  • 销毁:一次完整的运行循环结束之前,会被销毁

2. 以上代码是否有问题?如果有,如何解决?

  1. for (long i = 0; i < largeNumber; ++i) {
  2. NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
  3. str = [str uppercaseString];
  4. str = [str stringByAppendingString:@" - world"];
  5. }

解决方法:引入自动释放池

  • 1> 外面加自动释放池(快?):能够保证for循环结束后,内部产生的自动释放对象,都会被销毁,需要等到 for 结束后,才会释放内存
  • 2> 里面加自动释放池(慢?):能够每一次 for 都释放产生的自动释放对象!
  1. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  2. NSLog(@"start");
  3. CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
  4. [self answer1];
  5. NSLog(@"外 %f", CFAbsoluteTimeGetCurrent() - start);
  6. start = CFAbsoluteTimeGetCurrent();
  7. [self answer2];
  8. NSLog(@"内 %f", CFAbsoluteTimeGetCurrent() - start);
  9. }
  10. - (void)answer1 {
  11. @autoreleasepool {
  12. for (long i = 0; i < largeNumber; ++i) {
  13. NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
  14. str = [str uppercaseString];
  15. str = [str stringByAppendingString:@" - world"];
  16. }
  17. }
  18. }
  19. - (void)answer2 {
  20. for (long i = 0; i < largeNumber; ++i) {
  21. @autoreleasepool {
  22. NSString *str = [NSString stringWithFormat:@"hello - %ld", i];
  23. str = [str uppercaseString];
  24. str = [str stringByAppendingString:@" - world"];
  25. }
  26. }
  27. }
  • 实际测试结果,是运行循环放在内部的速度更快!
  • 日常开发中,如果遇到局部代码内存峰值很高,可以引入自动释放池及时释放延迟释放对象