创建内存积累的 AFHTTPRequestOperations 队列

Posted

技术标签:

【中文标题】创建内存积累的 AFHTTPRequestOperations 队列【英文标题】:Queue of AFHTTPRequestOperations creating Memory Buildup 【发布时间】:2014-03-06 17:41:00 【问题描述】:

我刚刚更新到 AFNetworking 2.0,我正在重写我的代码以下载数据并将其插入 Core Data。

我下载 JSON 数据文件(10-200mb 文件),将它们写入磁盘,然后将它们传递给后台线程来处理数据。下面是下载 JSON 并将其写入磁盘的代码。如果我只是让它运行(甚至不处理数据),应用程序会耗尽内存直到它被杀死。

我假设当数据进入时,它被存储在内存中,但是一旦我保存到磁盘,为什么它会留在内存中?自动释放池不应该处理这个吗?我还将 responseData 和 downloadData 设置为 nil。有什么明显的地方我做错了吗?

@autoreleasepool

    for(int i = 1; i <= totalPages; i++)
    
        NSString *path = ....
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:path]];
        AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
        op.responseSerializer =[AFJSONResponseSerializer serializer];

        [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) 
        
            //convert dictionary to data 
            NSData *downloadData = [NSKeyedArchiver archivedDataWithRootObject:responseObject];

            //save to disk
            NSError *saveError = nil;
            if (![fileManager fileExistsAtPath:targetPath isDirectory:false])
            
                [downloadData writeToFile:targetPath options:NSDataWritingAtomic error:&saveError];
                if (saveError != nil) 
                
                    NSLog(@"Download save failed! Error: %@", [saveError description]);
                
            

            responseObject = nil;
            downloadData = nil;

         failure:^(AFHTTPRequestOperation *operation, NSError *error) 
            DLog(@"Error: %@", error);
        ];
    
    [mutableOperations addObject:op];


NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) 
    DLog(@"%lu of %lu complete", (unsigned long)numberOfFinishedOperations, (unsigned long)totalNumberOfOperations);
 completionBlock:^(NSArray *operations) 
    DLog(@"All operations in batch complete");
];

mutableOperations = nil;
[manager.operationQueue addOperations:operations waitUntilFinished:NO];

谢谢!

编辑 #1 在我的完整块中添加@autoreleasepool 似乎会稍微减慢内存使用量,但它仍然会累积并最终导致应用程序崩溃。

【问题讨论】:

【参考方案1】:

如果您的 JSON 文件每个都是 10-200mb,这肯定会导致内存问题,因为这种请求会将响应加载到内存中(而不是将它们流式传输到持久存储)。更糟糕的是,因为你使用 JSON,我认为问题是两倍,因为你将把它加载到字典/数组中,这也会占用内存。所以,如果你有四个 100mb 的下载,你的峰值内存使用量可能是 800mb 的数量级(NSData 为 100mb 加上数组/字典的 ~100mb(可能更大),四个乘以四并发请求)。您可能很快就会耗尽内存。

所以,有几个反应:

    在处理如此大量的数据时,您可能希望采用流式接口(@98​​7654326@ 或 NSURLSessionDataTask,您可以在其中写入数据,而不是将其保存在内存中;或使用 @987654328 @ 为您执行此操作),将数据直接写入持久存储(而不是在下载时尝试将其保存在 RAM 中的 NSData 中)。

    如果你使用NSURLSessionDownloadTask,这真的很简单。如果您需要支持 7.0 之前的 ios 版本,我不确定 AFNetworking 是否支持将响应直接流式传输到持久存储。我打赌您可以编写自己的响应序列化程序来做到这一点,但我还没有尝试过。我一直在编写自己的 NSURLConnectionDataDelegate 方法,这些方法可以直接下载到持久存储(例如 like this)。

    您可能不想为此使用 JSON(因为 NSJSONSerialization 会将整个资源加载到内存中,然后将其解析为 NSArray/NSDictionary,也在内存中),而是使用以下格式适合对响应进行流式解析(例如 XML),并编写一个解析器,在解析时将数据存储到您的数据存储(Core Data 或 SQLite),而不是尝试将整个内容加载到 RAM 中。

    注意,即使NSXMLParser 的内存效率也出奇的低(参见this question)。在XMLPerformance 示例中,Apple 演示了如何使用更麻烦的LibXML2 来最小化 XML 解析器的内存占用。

    顺便说一下,我不知道您的 JSON 是否包含您已编码的任何二进制数据(例如 base 64 等),但如果是这样,您可能需要考虑一种不包含的二进制传输格式做这个转换。使用 base-64 或 uuencode 或任何可以增加带宽和内存要求的方法。 (如果你处理的不是已经编码的二进制数据,那么忽略这一点。)

    顺便说一句,您可能希望使用 Reachability 来确认用户的连接类型(Wifi 与蜂窝),因为通过蜂窝下载这么多数据被认为是一种不好的形式(至少在未经用户许可的情况下),不仅由于速度问题,而且还有用尽运营商每月数据计划的过多部分的风险。我什至听说 Apple 历来拒绝尝试通过蜂窝网络下载过多数据的应用程序。

【讨论】:

感谢您的信息。在 AFNetworking 2.0 之前,我做了 NSOperation 的子类,并使用 NSURLConnection 来处理下载.. 附加数据。 (虽然,当它完成下载时,我仍然将它保存到磁盘)。这样做,我的内存从未超过 50mb,这就是我如此困惑的原因。我将尝试使用 AFNetworking 实现 NSURLSessionDownloadTask 以查看是否有帮助。谢谢! 我最终更新了我的旧 NSOperation 子类以高效工作。我没有看到这样做的巨大内存积累。

以上是关于创建内存积累的 AFHTTPRequestOperations 队列的主要内容,如果未能解决你的问题,请参考以下文章

js中闭包(积累总结)

Apache Spark Streaming:在内存中积累数据并在很久以后才输出

Python 零碎知识积累 III

常用 前端开发 知识积累(推荐收藏)

内存泄露检测工具Valgrind

python积累