【问题标题】:How to solve a memory leak on a NSFilehandle如何解决 NSFilehandle 上的内存泄漏
【发布时间】:2013-07-14 18:50:38
【问题描述】:

我正在开发一个 OS X 应用程序,该应用程序始终运行并处理他队列中的所有内容。这些作业将其进程记录在标准输出中。我正在尝试使用在后台线程中运行的 while 循环实时从标准输出中获取此信息。问题是运行 12 小时后,内存使用量从 25MB 上升到 >750MB。该应用程序正在使用 ARC,所以我在这里有点迷路。任何人都知道如何解决这个问题?

NSFileHandle *fileStd = [stdPipe fileHandleForReading];

NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: cmd];
[task setArguments: arguments];

NSPipe *stdPipe = [NSPipe pipe];
NSPipe *errPipe = [NSPipe pipe];

[task setStandardOutput: stdPipe];
[task setStandardError: errPipe];

NSFileHandle *fileStd = [stdPipe fileHandleForReading];

[task launch];

NSData *readData;
NSString *logString;

while ((readData = [fileStd availableData]) && [readData length]){

   logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
        [self parseLog:logString];
    }

    readData = nil;
}

[task waitUntilExit];

还有parseLog方法:

+ (void) parseLog:(NSString *)output {

    NSArray *chunks = [output componentsSeparatedByString: @"\r"];
    NSString *lastLine = [chunks lastObject];

    [lastLine writeToFile:@"/tmp/status.log" atomically:YES encoding:NSUTF8StringEncoding error:nil];

    NSRange textRange = [lastLine rangeOfString:@"<TEST> "];
    if(textRange.location != NSNotFound) {

        NSMutableDictionary *info = [NSMutableDictionary dictionary];
        [info setObject:lastLine forKey:@"test"];

        [[NSNotificationCenter defaultCenter] postNotificationName:@"handleNotification" object:nil userInfo:info];
    }
}

【问题讨论】:

  • 您是否尝试使用 xcode 工具来检测泄漏?
  • 是的,我做到了,这就是我结束的地方。我被困了几天,尝试了所有不同的东西。

标签: objective-c macos memory-leaks


【解决方案1】:

ARC 仍然在“幕后”使用引用计数。特别是,它仍然使用自动释放池,并且您不会在任何地方耗尽任何自动释放池。尝试用 @autoreleasepool 块封闭你的 while 循环体。

while ((readData = [fileStd availableData]) && [readData length]){
    @autoreleasepool
    {

       logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
       [self parseLog:logString];
    }
}

这相当于在循环开始时创建一个自动释放池并在结束时将其排空。

编辑

正如 bbum 所说,readData 在每次迭代时都会放入一个自动释放池中,但它在 @autoreleasepool 位之外,因此代码需要重组:

bool dataToProcess = true;
while (dataToProcess){
    @autoreleasepool
    {
       NSData* readData = [fileStd availableData];
       dataToProcess = [readData length] > 0;
       if (dataToProcess)
       {
           logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
           [self parseLog:logString];
       }
    }
}

【讨论】:

  • 我已经在 while 循环周围使用 autoreleasepool 进行了尝试,但没有成功。而且你的方法也不起作用。内存仍在以每分钟 1MB 的速度增长。
  • readData 是导致内存增长的原因。它需要在定期刷新的自动释放池中。循环外的自动释放池无济于事,而循环内的自动释放池也无济于事。
  • @bbum 好地方,我试试别的。
【解决方案2】:

使用分配工具查看堆增长的原因。

特别是,您可以使用 Heapshot 分析非常有效地找出此类问题。

Here is a write-up.

简而言之:

  • 在 Allocations Instrument 下启动您的应用
  • (1) 按下标记堆按钮
  • 等一下
  • 转到 (1)

重复一段时间。在您的情况下,您可能需要每 20 分钟到一个小时按一次 标记堆 按钮。


杰里米几乎做对了。你需要稍微重构你的循环:

while (1) {
@autoreleasepool {
   if (readData = [fileStd availableData]) && [readData length]){

   logString = [[NSString alloc] initWithData: readData encoding: NSUTF8StringEncoding];
        [self parseLog:logString];
    }

    readData = nil;
   } else {
    break;
   }
}

readData 需要在池中。

【讨论】:

  • 太好了,我试试看。
  • 如前所述,我试了一下,发现问题似乎出在以下语句中:while ((readData = [fileStd availableData]) && [readData length]){} 但为什么我不能t 仍然发现。
  • 是的,对循环进行一点重构就可以了。谢谢!
  • 我刚刚用 Swift 遇到了这个问题,这解决了它!在 Swift 中它只是 autoreleasepool { ... },没有 "@"
猜你喜欢
  • 2015-06-28
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 2018-04-11
  • 2020-04-07
  • 1970-01-01
相关资源
最近更新 更多