循环中的@autoreleasepool 块不会减少内存峰值

@autoreleasepool block in loop dose not reduce memory peak

我被告知循环中的@autoreleasepool 块可以减少内存使用的峰值,直到我进行测试。测试设备是iPhone 6s with iOS 11.4.1.

我的代码:

@implementation BigMemObj{
    NSMutableArray *_mutArr;
}

-(instancetype)init{
    if(self = [super init]){
        _mutArr = [[NSMutableArray alloc] initWithCapacity:1024*1024*30];
        for(int i = 0; i < 1024*1024*30; i++){
            [_mutArr addObject:@(i)];
        }
    }

    return self;
}


- (void)viewDidLoad {

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(int i = 0 ; i < 10000; i++){
        @autoreleasepool {
            BigMemObj *mem = [[BigMemObj alloc] init];
        }
    }
}

- (void)viewDidLoad {

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(int i = 0 ; i < 10000; i++){
           BigMemObj *mem = [[BigMemObj alloc] init];
    }
}

我运行都测试了34秒,在测试1中,内存占用最高为458M,而在测试2中,内存占用最高为362M。并且两个测试都是三角形。

使用@autoreleaspool 块

没有@autoreleaspool 块

autoreleasepool 实现是否更改?还是编译器做了一些优化?

谢谢!

其实一切看起来都很正常。您看到的增长是这部分:

_mutArr = [[NSMutableArray alloc] initWithCapacity:1024*1024*30];
for(int i = 0; i < 1024*1024*30; i++){
    [_mutArr addObject:@(i)];
}

所以在这里你将你的数字添加到一个数组 _mutArr 并且你添加了 1024*1024*30 个数组。当此循环完成时 _mutArr 有效且已满,它会保留所有这些数字。这甚至不会通过在此循环中添加另一个自动释放池来改变,因为您的数组不会释放这些数字。

现在调用此构造函数后,您有

@autoreleasepool {
    BigMemObj *mem = [[BigMemObj alloc] init];
}

所以自动释放池将在这一刻被耗尽,释放 BigMemObj 实例 mem 中的所有数字,你的内存恢复正常。

您可能希望您的应用程序在不调用 @autoreleasepool 的情况下在内存中保持增长。但是,如果您删除该调用,则根本没有任何变化。原因是您的代码 none 根本使用了自动释放池。您的代码转换为(非 ARC)的内容是:

NSMutableArray *arr = [[NSMutableArray alloc] init];
for(int i = 0 ; i < 10000; i++){
    @autoreleasepool {
        BigMemObj *mem = [[BigMemObj alloc] init];
        [mem release];
    }
}
[arr release];

但只有

时才需要 autoreleasepool
NSMutableArray *arr = [[NSMutableArray alloc] init];
for(int i = 0 ; i < 10000; i++){
    @autoreleasepool {
        BigMemObj *mem = [[[BigMemObj alloc] init] autorelease];
    }
}
[arr release];

需要自动释放池的情况:

NSMutableArray *allBigValues = [[NSMutableArray alloc] init];

NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"profile.png"];

for(int i = 0; i<100000; i++){
    @autoreleasepool {
        [allBigValues addObject:[UIImage imageWithContentsOfFile:path]];
        [allBigValues removeAllObjects];
    }
}

如果在此代码中删除自动释放池,它将在内存中增长,直到循环结束。这样做的原因是因为 imageWithContentsOfFile 正在使用自动释放池,并且此方法生成的所有图像只有在池被排空后才会被释放。由于主池不会在循环内释放,我们需要创建另一个。

最重要的是,此代码运行良好,但一旦您删除 @autoreleasepool 部分,它就会开始在内存中增长并可能使您的应用程序崩溃。

注意 1: 您需要将图像 profile.png 添加到您的应用程序中,此代码才能正常工作(只需将其拖到源文件中,而不是资产中)。

注意 2: 我们在涉及池的地方使用 "drain" 因为这曾经是您需要池时需要调用的方法的名称删除它的对象。过去是这样的:

for(int i = 0; i<100000; i++){
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [allBigValues addObject:[UIImage imageWithContentsOfFile:path]];
    [allBigValues removeAllObjects];
    [pool drain];
}