objective-c - stringByAppendingString 导致系统用完应用程序的内存

标签 objective-c

当我在一个“大”(37000 行)文本文件上运行以下代码时,有人能指出为什么我的系统告诉我应用程序内存不足吗?

-(void) writeToFile: (NSString*)filePath withSeparator:(NSString*) fieldSep{
NSString* completeFile = [[[NSString alloc] initWithString:@""] autorelease];
for(int i=0;i<[self numberOfRows];i++){
    printf("im at line number... %i of %i\n",i,[self numberOfRows]);
    for(int j=0;j<[self numberOfColumns];j++){
        completeFile = [completeFile stringByAppendingString:[self objectInRow:i column:j]];

        if(j<[self numberOfColumns]-1){
            //separator for all columns except last one
            completeFile = [completeFile stringByAppendingString:fieldSep];
        }
    }
        completeFile = [completeFile stringByAppendingString:@"\n"];
}
NSError *error = nil;
[completeFile writeToFile:filePath atomically:NO
                 encoding:NSStringEncodingConversionAllowLossy error:&error];
if(error){
    NSLog(@"Error writing file at %@\n%@",
          filePath, [error localizedFailureReason]);
}

出于调试原因,我添加了 printf,前 4000 行似乎立即发生,然后慢慢减慢...我的文件包含超过 37000 行,与这些类似:

1893-11-6   136 194 165

最佳答案

当您使用工厂方法分配对象时,对象会添加到自动释放池中。 autoreleasepool 仅在您的事件循环运行时被耗尽,在您的 IBAction 返回后。

这里的技巧是将循环的内容放入它自己的自动释放池中。

但让我们先解决最大的问题。您应该在此处使用一个 NSMutableString 类,它将大大减少您需要创建的对象数量。

我们将把 completeFile 转换为 NSMutableString,使用工厂方法构造,然后附加到它:

-(void) writeToFile: (NSString*)filePath withSeparator:(NSString*) fieldSep{
    NSMutableString* completeFile = [NSMutableString string];
    for(int i=0;i<[self numberOfRows];i++){
        printf("im at line number... %i of %i\n",i,[self numberOfRows]);
        for(int j=0;j<[self numberOfColumns];j++){
            [completeFile appendString:[self objectInRow:i column:j]];

            if(j<[self numberOfColumns]-1){
                //separator for all columns except last one
                completeFile appendString:fieldSep];
            }
        }
            [completeFile appendString:@"\n"];
    }
    NSError *error = nil;
    [completeFile writeToFile:filePath atomically:NO
                     encoding:NSStringEncodingConversionAllowLossy error:&error];
    if(error){
        NSLog(@"Error writing file at %@\n%@",
              filePath, [error localizedFailureReason]);
    }
}

不过,这会留下另一个问题。看到 [self objectInRow:i column:j] 了吗?它仍然(大概)是一个自动释放的对象。那不会被清理干净。

我们可能已经让您的代码运行而不会崩溃,这取决于数据的大小,但这是何时崩溃的问题,而不是如果崩溃。

为了解决这个问题,我们需要引入自动释放池。让我们每行每列做一个。这可能看起来过分(并且,事实上,在这种情况下是因为我们已经消除了在外循环中使用 autoreleasepool)但是 autoreleasepools 非常便宜。如果您要对大量数据进行循环,这是一种很好的做法。

您可以用 @autorelease block 替换每个 for block ,例如:

for(int i=0;i<[self numberOfRows];i++){

与:

for(int i=0;i<[self numberOfRows];i++) @autoreleasepool {

这给了我们这个代码:

-(void) writeToFile: (NSString*)filePath withSeparator:(NSString*) fieldSep{
    NSMutableString* completeFile = [NSMutableString string];
    for(int i=0;i<[self numberOfRows];i++) @autoreleasepool {
        printf("im at line number... %i of %i\n",i,[self numberOfRows]);
        for(int j=0;j<[self numberOfColumns];j++) @autoreleasepool {
            [completeFile appendString:[self objectInRow:i column:j]];

            if(j<[self numberOfColumns]-1){
                //separator for all columns except last one
                completeFile appendString:fieldSep];
            }
        }
            [completeFile appendString:@"\n"];
    }
    NSError *error = nil;
    [completeFile writeToFile:filePath atomically:NO
                     encoding:NSStringEncodingConversionAllowLossy error:&error];
    if(error){
        NSLog(@"Error writing file at %@\n%@",
              filePath, [error localizedFailureReason]);
    }
}

不过,最后一点。您在此处的错误检查不安全。未定义在成功 时像这样传入的错误指针会发生什么情况。

    [completeFile writeToFile:filePath atomically:NO
                     encoding:NSStringEncodingConversionAllowLossy error:&error];
    if(error){
        NSLog(@"Error writing file at %@\n%@",
              filePath, [error localizedFailureReason]);
    }

相反,你想要这样:

    BOOL ok = [completeFile writeToFile:filePath atomically:NO
                     encoding:NSStringEncodingConversionAllowLossy error:&error];
    if(!ok){
        NSLog(@"Error writing file at %@\n%@",
              filePath, [error localizedFailureReason]);
    }

那么,这应该做你想做的。

关于objective-c - stringByAppendingString 导致系统用完应用程序的内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20081690/

相关文章:

ios - 获取UIWebView内容并转为图片

iphone - NSArray 的前三项变成 NSString?

iphone - 如何将字符串转换为带分隔符的数组?

ios - 是否可以使背景 ViewController 不可点击?

objective-c - 读取 9 位、10 位或 11 位(精确) block 中的数据并在 Objective-C/C 中转换为整数

ios - UITableViewAutomaticDimension 和多行标签问题

objective-c - Apple Watch 图像未在模拟器上显示

ios - 每次保存新数据时 Plist 都会重写旧数据

objective-c - 使用 ARC 从导航 Controller 弹出时保留 View Controller

objective-c - 自定义 DatePicker 的算法