解码一个巨大的NSString,内存不足

时间:2012-12-03 21:11:00

标签: ios xml memory-management base64 nsdata

我正在寻找有关如何改进使用base64编码解码40 + MB NSString并将其保存到文件的过程的想法,同时能够使进程适应iPad 1的256 MB RAM

我从NSXMLParser获取NSString:

id pointerToString;

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if ([currentElement isEqualToString:@"myElement"]) 
    {
    pointerToString = [string retain];
}
}

然后我在回调中使用pointerToString:

[handler performSelector: action withObject: pointerToString];

在回调中(id值为pointerToString)。我使用pointerToString初始化NSData,同时使用base64编码对其进行解码。

^(id value)
{
    if ( [[value class] isSubclassOfClass:[NSString class]] ) 
    {
    NSData *data = [NSData dataFromBase64String:value];
    [data writeToFile:file.path atomically:YES];
}
}

当NSData调用之后或期间内存分配达到130MB左右时,iPad 1设备内存不足并被iOS杀死。

我已经确定,为了以这种方式处理40 + MB NSString,我需要大约180 + MB的RAM(这是iPad 2& 3上的最大内存分配,这个过程有效,因为更多RAM)

任何想法/提示?

谢谢

2 个答案:

答案 0 :(得分:2)

修改

当处理这个大小的文件时,你可能不希望一次在内存中加载整个多兆字节文件,既不是巨大的输入文件也不是几乎同样巨大的输出文件。您应该以流式方式解析此问题,在进行时解码foundCharacters中的数据,而不是在内存中保留任何重要部分。

但是,传统技术可能会在整个过程的三个阶段保存整个XML文件内存:

  1. 从服务器下载XML文件时;

  2. 当XML解析器解析该文件时;以及

  3. 当你对文件进行Base64解码时。

  4. 诀窍是采用流技术,一次完成这三个进程,用于单个大型XML文件的小块。最重要的是,当您下载整个50mb文件时,抓取几个kb,解析XML,如果您正在解析Base64编码的字段,请执行Base64-decode为几kb,然后继续下一步大块数据。

    有关此示例(至少是流式XML下载和解析,不包括Base64解码),请参阅Apple的XMLPerformance sample project。您将看到它将演示两个XML解析器,我们都熟悉的NSXMLParser,以及不太熟悉的LibXML解析器。 NSXMLParser的问题是,留给它自己的设备,即使你使用initWithContentsOfURL,它也会在开始解析之前将整个XML文件加载到内存中。

    在我之前的回答中,我错误地声称通过使用initWithContentsOfURLNSXMLParser会在下载时将精美的小数据包解析为URL的内容。 foundCharacters协议的NSXMLParserDelegate方法似乎与NSURLConnectionDelegate方法didReceiveData类似,我确信NSXMLParser将像处理流一样处理流NSURLConnection确实如此,即在下载过程中返回信息。可悲的是,它没有。

    通过使用LibXML,与Apple XMLPerformance示例项目一样,您实际上可以使用流的NSURLConnection功能,从而动态解析XML。

    我创建了一点test project,但我可能会建议您详细介绍Apple的XMLPerformance示例项目。但是在我的实验中,一个56mb的XML文件在通过NSXMLParser进行解析和转换时消耗超过100mb,但在使用LibXML2时只消耗了2mb。


    在您的评论中,您描述了将Base64编码数据下载到文件然后解码的愿望。这种方法效率似乎低得多,但肯定可行。顺便说一句,在初始下载时,你有相同的内存问题(我在上面解决)。我强烈建议您确保初始下载Base64编码的数据并不像大多数例程那样轻松地将其加载到RAM中。假设您正在使用NSURLConnection,您希望在收到NSOutputStream中的数据时将数据写入didReceiveData,而不是将其保存在RAM中。

    请参阅Apple AdvancedURLConnections example的AdvancedGetController.m中的didReceiveResponse,了解如何在收到文件时编写文件,而不是将文件添加到NSMutableData的典型模式(因为大多数这些例程只是假设你正在处理一个合理大小的文件)。 (忽略AdvancedURLConnections示例中关于身份验证等的所有内容,但要集中精力了解它如何写入NSOutputStream。这种技术将解决此处列出的三个问题中的第一个问题。回答,但不是后两者。为此,您必须考虑使用Apple的XMLPerformance示例项目或其他类似技术中所示的LibXML2

答案 1 :(得分:0)

方法

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string

可能不会立即收到所有数据。  Doc说的是

由解析器对象发送,为其委托提供表示当前元素的全部或部分字符的字符串。

所以它被多次调用。 看起来你正试图一次写完整个字符串(对不起,如果我错了)。 因此,您可以通过执行以下操作将收到的数据附加到文件中:

您可以使用

的组合
-writeData: 

-seekToEndOfFile 

NSFileHandle类中用于将NSData写入文件末尾的方法。

但请小心使用base64编码进行部分数据接收!