ios - 创建内存累积的 AFHTTPRequestOperations 队列

标签 ios memory-management nsoperationqueue afnetworking-2 afhttprequestoperation

我刚刚更新到 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 在我的完整 block 中添加 @autoreleasepool 似乎会稍微减慢内存使用量,但它仍然会累积并最终导致应用程序崩溃。

最佳答案

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

所以,有一些 react :

  1. 在处理如此大量的数据时,您需要采用流式接口(interface)(NSURLConnectionNSURLSessionDataTask,您可以在其中写入数据,而不是将其保存在内存中;或者使用 NSURLSessionDownloadTask 来为您完成此操作),将数据直接写入持久存储(而不是在下载时尝试将其保存在 RAM 中的 NSData 中)。

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

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

    注意,甚至NSXMLParser令人惊讶的是内存效率低下(参见 this question )。在 XMLPerformance示例,Apple演示了如何使用更麻烦的LibXML2最大限度地减少 XML 解析器的内存占用。

  3. 顺便说一句,我不知道您的 JSON 是否包含您编码的任何二进制数据(例如,base 64 等),但如果是这样,您可能需要考虑一种不包含任何二进制数据的二进制传输格式。不必进行此转换。使用 base-64 或 uuencode 或其他任何可以增加您的带宽和内存需求。 (如果您不处理已编码的二进制数据,请忽略这一点。)

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

关于ios - 创建内存累积的 AFHTTPRequestOperations 队列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22231971/

相关文章:

ios - 操作 isFinished 始终为 false

realm - 如何强制 NSOperationQueue 在一个线程上运行?

ios - 更新屏幕旋转时的 UITabBar 宽度

ios - 将 UIColor 与文本标签/字符串进行比较

ios - stringFromDate 在特殊设备上崩溃

iphone - 如何取消使用 addOperationWithBlock 创建的操作?

c# - Monotouch 自动增量内部版本号及版本号

c++ - 使用 Fortran 中的内存数据调用 C 代码

c - 指向结构的指针中的结构指针数组

C++测试矩阵是否已经初始化