Очередь AFHTTPRequestOperations, создающая наращивание памяти

Я только что обновился до AFNetworking 2.0, и я переписываю свой код, чтобы загрузить данные и вставить их в Core Data.

Я загружаю файлы данных JSON (где-нибудь из файлов размером 10-200 МБ), записываю их на диск, а затем передаю их фоновым потокам для обработки данных. Ниже приведен код, который загружает JSON и записывает его на диск. Если я просто позволю этому запускаться (даже не обрабатывая данные), приложение использует память до тех пор, пока оно не будет уничтожено.

Я предполагаю, что когда данные поступают, они сохраняются в памяти, но как только я сохраняю их на диск, почему они остаются в памяти? Разве пул авторелизов не должен позаботиться об этом? Я также установил responseData и downloadData в ноль. Есть ли что-то явно очевидное, что я здесь делаю не так?

@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-200 МБ каждый, это определенно вызовет проблемы с памятью, потому что такого рода запросы будут загружать ответы в память (а не передавать их в постоянное хранилище). Хуже того, потому что вы используете JSON, я думаю, что проблема в два раза хуже, потому что вы собираетесь загружать это в словарь / массив, который также занимает память. Таким образом, если у вас происходит четыре загрузки по 100 МБ, пиковое использование памяти может составить порядка 800 МБ (100 МБ для NSData плюс ~100 МБ для массива / словаря (возможно, намного больше), четыре раза для четырех одновременных запросов). Вы можете быстро исчерпать память.

Итак, пара реакций:

  1. При работе с этим объемом данных вы захотите использовать потоковый интерфейс (NSURLConnection или же NSURLSessionDataTask где вы записываете данные по мере их поступления, а не удерживаете их в памяти; или использовать NSURLSessionDownloadTask который делает это для вас), тот, который записывает данные непосредственно в постоянное хранилище (вместо того, чтобы пытаться сохранить его в NSData в оперативной памяти, как это загружается).

    Если вы используете NSURLSessionDownloadTaskэто действительно просто. Если вам нужно поддерживать версии iOS до 7.0, я не уверен, поддерживает ли AFNetworking потоковую передачу ответов непосредственно в постоянное хранилище. Держу пари, что вы могли бы написать свой собственный сериализатор ответов, который делает это, но я не пробовал. Я всегда писал свой собственный NSURLConnectionDataDelegate методы, которые загружаются непосредственно в постоянное хранилище (например, что-то вроде этого).

  2. Вы можете не захотеть использовать JSON для этого (потому что NSJSONSerialization загрузит весь ресурс в память, а затем проанализирует его NSArray/NSDictionary(также в памяти), но лучше использовать формат, который поддается потоковому анализу ответа (например, XML), и напишите синтаксический анализатор, который сохраняет данные в вашем хранилище данных (Core Data или SQLite) по мере их анализа, а не пытается загрузить все это в оперативной памяти.

    Обратите внимание, даже NSXMLParser на удивление неэффективна память (см. этот вопрос). В примере XMLPerformance Apple демонстрирует, как вы можете использовать более громоздкий LibXML2, чтобы минимизировать использование памяти вашего анализатора XML.

  3. Кстати, я не знаю, включает ли ваш JSON какие-либо двоичные данные, которые вы закодировали (например, base 64 или тому подобное), но если это так, вы можете рассмотреть двоичный формат передачи, который не должен делать это преобразование. Использование base-64 или uuencode или чего-либо еще может увеличить ваши требования к пропускной способности и памяти. (Если вы не имеете дело с двоичными данными, которые были закодированы, игнорируйте этот пункт.)

  4. Кроме того, вы можете использовать Reachability для подтверждения типа подключения пользователя (Wi-Fi против сотовой связи), потому что считается плохой формой загружать такое количество данных по сотовой сети (по крайней мере, без разрешения пользователя), не только из-за скорости проблемы, но и риск использования чрезмерной части ежемесячного тарифного плана их перевозчика. Я даже слышал, что Apple исторически отклоняла приложения, которые пытались загружать слишком много данных по сотовой связи.

Другие вопросы по тегам