@autoreleasepool使用时机

关注如何使用

1. 官方推荐时机

If you are writing a program that is not based on a UI framework, such as a command-line tool.

If you write a loop that creates many temporary objects.You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.

If you spawn a secondary thread.You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects. 

即以下三种情况

  • 非UI程序
  • 循环中嵌套大量临时对象时
  • 自己创建了一个辅助线程时

对于iOS程序来说,重点关注第二和第三种情况

2.第三方库使用举例

  1. SDWebImage(SDWebImageDecoder.m文件中,3.7.0版本)
+ (UIImage *)decodedImageWithImage:(UIImage *)image {

    if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
        return nil;
    }
    ...
    @autoreleasepool{
        CGImageRef imageRef = image.CGImage;

        CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
        BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                         alpha == kCGImageAlphaLast ||
                         alpha == kCGImageAlphaPremultipliedFirst ||
                         alpha == kCGImageAlphaPremultipliedLast);
        if (anyAlpha) {
            return image;
        }
        ...
        return imageWithoutAlpha;
    }
}

此处操作是生成解压缩图片,该方法会在SDWebImageDownloaderOperation connection:(NSURLConnection *)connection didReceiveData:(NSData *)data方法中不停回调,该方法调用会发生在子线程,同时方法内绘制bitmap会生成大量临时对象,符合第二、第三两种情况

  1. CocoaLumberjack gcd操作大量使用
- (void)addLogger:(id )logger withLevel:(DDLogLevel)level {
    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
        [self lt_addLogger:logger level:level];
    } });
}
- (void)removeLogger:(id )logger {
    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
        [self lt_removeLogger:logger];
    } });
}

dispatch_async使用自定义并发队列,系统并不会内部线程中添加autoreleasepool,所以手动添加autoreleasepool更好,此处如果使用globalQueue是不需要添加的,符合第三种使用条件

后续遇到继续添加使用场景

3.原理

参考:Objective-C Autorelease Pool 的实现原理

  • 释放时机
  1. 在出了autorelease代码块开始释放,系统对内部所有对象调用release方法(也就是临时对象得是autorelease的,才会释放),否则加autorelease依然无效
  2. 系统自带的autoreleasepool则是每次runloop循环结束释放
  3. 在iOS11.2环境下,下方viewDidAppear方法中并不会打印null,应该是主线程的runloop逻辑有变化。
__weak id reference = nil;
- (void)viewDidLoad {
    [super viewDidLoad];    
    NSString *str = [NSString stringWithFormat:@"sunnyxx"];    // str是一个autorelease对象,设置一个weak的引用来观察它
    reference = str;
}
- (void)viewWillAppear:(BOOL)animated {
        NSLog(@"%@", reference); // Console: sunnyxx
    }
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];    
    NSLog(@"%@", reference); // Console: (null)}
  • 内部机制
  1. 此代码块内部其实是objc_autoreleasePoolPush和objc_autoreleasePoolPop(context)配合操作,代码块开始调用push方法,代码块结束调用pop方法;
  2. push和pop两个方法其实是AutoreleasePoolPage(这是一个双向链表,每个占用4096Mb内存,除了自身占用外,剩余空间会分配给加入代码块的临时对象)的封装,该结构体的释放逻辑,可查看上边参考文献,很详细
  • 与runloop、thread的关系
  1. 主线程以及gcd gloableQueue中的子线程运行时,每个runloop开始时都会创建autoreleasepool

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

  1. 每个线程都会管理唯一的autoreleasepool(等于没说)

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.

  1. 每个线程都有自己的runloop,只不过有的没启动

Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed.

  1. 理解(不一定完全正确)

任何一个线程的runloop启动后,runloop会在循环开始时创建autoreleasepool,循环结束时释放autoreleasepool,runloop是内部autoreleasepool的管理者
此处参考经典文章中代码:深入理解RunLoop

你可能感兴趣的:(@autoreleasepool使用时机)