【问题标题】:@autoreleasepool block in loop dose not reduce memory peak循环中的@autoreleasepool 块不会减少内存峰值
【发布时间】:2019-03-12 16:56:51
【问题描述】:

有人告诉我,循环中的 @autoreleasepool 块可以减少内存使用的峰值,直到我进行测试。测试设备为 iOS 11.4.1 的 iPhone 6s。

我的代码:

@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。并且两个测试都是三角形。

使用@autorelaspool 块

没有@autoreleaspool 块

autoreleasepool 的实现是否发生了变化?还是编译器做了一些优化?

谢谢!

【问题讨论】:

  • ARC在使用对象的block完成后自动释放对象,所以不需要手动使用@autoreleasepool。

标签: ios nsautoreleasepool


【解决方案1】:

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

_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 的情况下在内存中保持增长。但是,如果您删除该呼叫,则根本没有任何变化。原因是您的代码根本没有使用自动释放池。您的代码转换为(非 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];
}

【讨论】:

  • 感谢您对 Matic Oblak 的解释。所以Xcode生成的main函数中的全局@autoreleasepool是给legacy用的吧?
  • @user1764186 不,仍然需要全局的。有时仍然需要本地的。这是你的具体情况,不需要那个。我不确定哪些方法仍然使用自动释放对象,但那些需要内部自动释放池的方法。由于编译器能够优化您的代码,因此有时很难找到/创建示例。
  • @user1764186 请检查我的编辑。我添加了一个示例,您需要使用额外的自动释放池和一些额外的解释。
  • 你的解释很清楚。非常感谢!在尝试了您的示例后,我发现了另一种情况:NSString create by stringWithFormat: generate autorelease objects too.
猜你喜欢
  • 2012-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-16
  • 2016-07-11
  • 2014-02-02
  • 1970-01-01
相关资源
最近更新 更多