AFHTTPRequestOperations的队列创建内存构建

时间:2014-03-06 17:41:00

标签: 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 在我的完整块中添加@autoreleasepool似乎会减慢内存使用量,但它仍会累积并最终导致应用程序崩溃。

1 个答案:

答案 0 :(得分:3)

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

所以,有几个反应:

  1. 在处理这一数据量时,您需要寻找一个流媒体界面(NSURLConnectionNSURLSessionDataTask,您可以在其中写入数据,而不是保留数据在内存中;或者使用为您执行此操作的NSURLSessionDownloadTask,将数据直接写入持久存储(而不是在下载时尝试将其保存在RAM中的NSData中)。

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

  2. 您可能不想为此使用JSON(因为NSJSONSerialization会将整个资源加载到内存中,然后将其解析为NSArray / NSDictionary,同样在内存),而是使用一种格式,它有助于流式解析响应(例如XML),并编写一个解析器,在解析数据时将数据存储到数据存储(Core Data或SQLite),而不是尝试加载RAM中的整件事。

    注意,即使NSXMLParser令人惊讶的是内存效率低下(参见this question)。在XMLPerformance示例中,Apple演示了如何使用更加繁琐的LibXML2来最小化XML解析器的内存占用。

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

  4. 另外,您可能希望使用Reachability来确认用户的连接类型(Wifi与手机),因为通过蜂窝网络下载这么多数据被认为是不好的形式(至少在没有用户许可的情况下) ,不仅是因为速度问题,还有使用其运营商月度数据计划过多部分的风险。我甚至听说苹果历来拒绝试图通过蜂窝网下载过多数据的应用程序。