Утечка памяти NSURLSession

Даже после аннулирования NSURLSession, запуская профиль с помощью инструментов, некоторые классы (вероятно, приватные), называемые TubeManager, HTTPConnectionCache и HTTPConnectionCacheDictionary, все еще сохраняются в памяти.

фрагмент кода для воспроизведения:

NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]];
NSURLSessionDataTask* sessionDataTask = [session dataTaskWithRequest:request
                                               completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
    [session finishTasksAndInvalidate];
}];
[sessionDataTask resume];

Instruments screenshot

Ответ 1

Итак, в чем вопрос? Вы хотите отключить кеширование? Сетевые ответы обычно кэшируются в NSURLCache как в памяти, так и в постоянном хранилище.

Если использование этого кеша проблематично, измените requestCachePolicy для конфигурации сеанса. Или измените cachePolicy a NSMutableURLRequest. Вы также можете настроить максимальный размер URLCache, который использует конфигурация сеанса, чтобы ограничить как объем использования ОЗУ, так и объем использования постоянной памяти.

Даже если вы отключите кеширование, как правило, не следует удивляться, что вызовы API увеличивают потребление памяти, не по своей вине. Это не редкость для приложения, чтобы испытать некоторое скромное потребление памяти при первом выполнении какой-либо задачи. Но не следует ожидать такого же роста в последующих итерациях, пока приложение работает. При отслеживании использования памяти обычно рекомендуется повторить задачу несколько раз и посмотреть, вернется ли приложение к некоторому устойчивому состоянию (что желательно), или оно продолжает расти (что требует дальнейшего изучения, чтобы убедиться, что ваш код не является источник проблемы). Но мы редко беспокоимся об исходном потреблении памяти, если это не драматично.

Глядя на ваш фрагмент кода, ничего явно не так. Я был бы склонен подозревать обычное потребление памяти iOS. Предполагая, что проблема шире, чем общее поведение кэша, если потребление памяти драматично и/или продолжает расти каждый раз, когда приложение выполняет этот код, затем предоставляйте более подробную информацию, и мы можем помочь вам диагностировать это дальше.


Вот как выглядит мой профиль памяти после четырех партий по 100 запросов каждый; плюс окончательный флаг после того, как я выпустил предупреждение о памяти:

enter image description here

(Обратите внимание, что это сложное изображение, чтобы я мог показать вам память перед первой партией, перед третьей партией, после последней партии и после того, как я вручную разместил предупреждение о памяти. Я объединил их, чтобы было легче увидеть, что общие ассигнования были в эти четыре момента времени.)

Ответ 2

Обратите внимание, что в iOS 9 среда безопасности выделяет appx. 4k данных кэша SSL и заряжает их в ваше приложение при первом возобновлении задачи для нового объекта NSURLSession. Apple Technical Q & QA1727 сообщает, что этот SSL-кеш сохраняется в течение 10 минут, несмотря ни на что, поскольку он является частным и полностью управляемым системой ( потому что безопасность!).

В вашем примере кода вы создаете новый объект NSURLSession каждый раз, когда вы делаете запрос. Но вы просто используете defaultSessionConfiguration и не указываете делегата, для которого может существовать сильная ссылка, тогда вместо этого нужно просто использовать singleton [NSURLSession sharedSession] и использовать resetWithCompletionHandler для очистки небезопасных распределений. Или настройте пользовательский синглтон, если вы хотите настроить конфигурацию.

  • (NSURLSession *) обсуждение разделов.

Для базовых запросов класс сеанса URL-адресов предоставляет общий синглтон который обеспечивает разумное поведение по умолчанию. Используя общий сеанс, вы можете получить содержимое URL-адреса в памяти с помощью всего несколько строк кода.

В отличие от других типов сеансов, вы не создаете общий сеанс; вы просто запрашиваете его, вызывая [NSURLSession sharedSession]. Как результат, вы не предоставляете делегат или объект конфигурации. Поэтому с общим сеансом:

   - You cannot obtain data incrementally as it arrives from the server.
   - You cannot significantly customize the default connection behavior.
   - Your ability to perform authentication is limited.
   - You cannot perform background downloads or uploads while your app is    
     not running.

Общий сеанс использует общий NSURLCache, NSHTTPCookieStorage, и объекты NSURLCredentialStorage, использует общую настраиваемую сеть Список протоколов (настроен с registerClass: и unregisterClass:), и основывается на конфигурации по умолчанию.

При работе с общим сеансом вам следует избегать настройка кеша, хранилища файлов cookie или хранилища учетных данных (кроме вы уже делаете это с NSURLConnection), потому что theres очень хороший шанс, что вы в конечном итоге перерастите возможности по умолчанию, в этот момент вам придется переписать все эти настройки в соответствии с вашими пользовательскими сеансами URL.

Другими словами, если вы делаете что-либо с кешами, куки, аутентификации или пользовательских сетевых протоколов, вероятно, вам следует используя сеанс по умолчанию вместо сеанса по умолчанию.

(Из ссылки класса NSURLSession... Курсив мой, P)

Если вы не используете синглтон с поддержкой sharedSession Apple, вам следует хотя бы взять сигнал от Apple и перевернуть свой собственный синглтон с свойством сеанса. Пункт сеанса состоит в том, что он предназначен для жизни дольше, чем один запрос. Несмотря на их нечеткую документацию, тот факт, что Apple предоставляет одноэлементный режим и называет его "сеансом", указывает, что объекты сеанса должны были жить дольше, чем только один запрос.

Да, вы должны быть invalidateAndCancel в какой-то момент, но не после каждого отдельного запроса, и даже если каждый запрос не собирается на другой сервер полностью (что почти никогда не бывает). Вам нужно только аннулировать и отменить, если вы собираетесь разорвать вашу ссылку на определенный сеанс; в противном случае вы можете просто вызвать flushWithCompletionHandler или resetWithCompletionHandler в сеансе, чтобы очистить выделение кучи сеанса до VM или reset, чтобы очистить хранилище кучи и VM. (Также см. Мой ответ здесь.)

Ответ 3

finishTasksAndInvalidate вызывается в неправильном месте... completeHandler для обработки ответа не имеет ничего общего с сеансом

Вот правильный код:

NSURLSessionConfiguration* config = [NSURLSessionConfigurationdefaultSessionConfiguration];
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"]];
NSURLSessionDataTask* sessionDataTask = [session dataTaskWithRequest:request
                                           completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
   // handle response...
}];
[sessionDataTask resume];
[session finishTasksAndInvalidate];