我刚刚更新到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
似乎会减慢内存使用量,但它仍会累积并最终导致应用程序崩溃。
答案 0 :(得分:3)
如果你的JSON文件每个真的是10-200mb,这肯定会导致内存问题,因为这种请求会将响应加载到内存中(而不是将它们传输到持久存储)。更糟糕的是,因为你使用JSON,我认为问题是两倍的问题,因为你将把它加载到字典/数组中,这也会占用内存。因此,如果你有四个100mb的下载,你的峰值内存使用量可能是800mb的数量级(NSData
的100mb加上阵列/字典的100mb(可能更大),时间为4四个并发请求)。你可能很快就会耗尽内存。
所以,有几个反应:
在处理这一数据量时,您需要寻找一个流媒体界面(NSURLConnection
或NSURLSessionDataTask
,您可以在其中写入数据,而不是保留数据在内存中;或者使用为您执行此操作的NSURLSessionDownloadTask
,将数据直接写入持久存储(而不是在下载时尝试将其保存在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与手机),因为通过蜂窝网络下载这么多数据被认为是不好的形式(至少在没有用户许可的情况下) ,不仅是因为速度问题,还有使用其运营商月度数据计划过多部分的风险。我甚至听说苹果历来拒绝试图通过蜂窝网下载过多数据的应用程序。