Постоянно растущее выделение памяти при загрузке изображений через HTTP в iOS

Я реализую приложение для iOS, которое должно получать огромное количество изображений по HTTP. Я попробовал несколько подходов, но независимо от того, что я делаю, Instuments показывает постоянно увеличивающееся распределение памяти, и приложение рано или поздно падает, когда я запускаю его на устройстве. Нет утечек, показанных приборами.

До сих пор я попробовал следующие подходы:

  • Получить изображения с помощью синхронного NSURLConnection в рамках NSOperation
  • Получить изображения с помощью асинхронного NSURLConnection в NSOperation
  • Получите изображения, используя [NSData dataWithContentsOfURL:url] в Main-Thread
  • Получите изображения, используя синхронный запрос ASIHTTPRequest в NSOperation
  • Получить изображения с помощью асинхронного запроса ASIHTTPRequest и добавить его в NSOperationQueue
  • Получите изображения, используя асинхронный ASIHTTPRequest и завершая блокировку

Дерево вызовов в Instrumetns показывает, что память используется во время обработки HTTP-ответа. В случае асинхронного NSURLConnection это в

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}  

В случае синхронного NSURLConnection, Instruments показывает растущую запись CFData (хранилище). Проблема с ASIHTTPRequest кажется такой же, как и с асинхронным NSURLConnection в аналогичной позиции кода. Подход [NSData dataWithContentsOfURL:url] показывает увеличение общего объема выделяемой памяти именно в этом операторе.

Я использую NSAutoReleasePool, когда запрос выполняется в отдельном потоке, и я попытался освободить память с помощью [[NSURLCache sharedURLCache] removeAllCachedResponses] - безуспешно.

Любые идеи / советы по решению проблемы? Благодарю.

Редактировать: поведение отображается только в том случае, если я сохраняю изображения с помощью CoreData. Вот код, который я запускаю как NSInvocationOperation:

-(void) _fetchAndSave:(NSString*) imageId {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *url = [NSString stringWithFormat:@"%@%@", kImageUrl, imageId];
HTTPResponse *response = [SimpleHTTPClient GET:url headerOrNil:nil];
NSData *data = [response payload];

if(data && [data length] > 0) {
    UIImage *thumbnailImage = [UIImage imageWithData:data];
    NSData *thumbnailData = UIImageJPEGRepresentation([thumbnailImage scaleToSize:CGSizeMake(55, 53)], 0.5); // UIImagePNGRepresentation(thumbnail); 

    [self performSelectorOnMainThread:@selector(_save:) withObject:[NSArray arrayWithObjects:imageId, data, thumbnailData, nil] waitUntilDone:NO];
}
[pool release];
}

Все связанные с CoreData вещи выполняются здесь в главном потоке, поэтому не должно быть никаких проблем с многопоточностью CoreData. Однако, если я сохраню изображения, Instruments показывает постоянно увеличивающиеся выделения памяти в позициях, описанных выше.

Редактировать II:

Код, связанный с CoreData:

-(void) _save:(NSArray*)args {
NSString *imageId = [args objectAtIndex:0];
NSData *data = [args objectAtIndex:1];
NSData *thumbnailData = [args objectAtIndex:2];

Image *image = (Image*)[[CoreDataHelper sharedSingleton] createObject:@Image];
image.timestamp =  [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]];
image.data = data;

Thumbnail *thumbnail = (Thumbnail*)[[CoreDataHelper sharedSingleton] createObject:@"Thumbnail"];
thumbnail.data = thumbnailData;
thumbnail.timestamp = image.timestamp;
[[CoreDataHelper sharedSingleton] save];
}

Из CoreDataHelper (self.managedObjectContext выбирает NSManagedObjectContext, пригодный для использования в текущем потоке):

-(NSManagedObject *) createObject:(NSString *) entityName {
return [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.managedObjectContext];
}

2 ответа

У нас была похожая проблема. При извлечении большого количества изображений через http наблюдался огромный рост и пилообразное распределение памяти. Мы увидим, как система очистится, более или менее, как она пошла, но медленно, и не предсказуемо. Тем временем загружались потоки, накапливались все, что держалось в памяти. Выделение памяти может составить около 200 миллионов, и тогда мы умрем.

Проблема была в NSURLCache. Вы заявили, что пытались [[NSURLCache sharedURLCache] removeAllCachedResponses]. Мы тоже это попробовали, но потом попробовали что-то немного другое.

Наши загрузки выполняются в группах из N изображений / фильмов, где N обычно составляет от 50 до 500. Было важно, чтобы мы получили весь N как атомарную операцию.

Прежде чем мы начали нашу группу загрузок http, мы сделали это:

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0];
[NSURLCache setSharedURLCache:sharedCache];

Затем мы получаем каждое изображение в N по http с помощью синхронного вызова. Мы делаем эту групповую загрузку в NSOperation, поэтому мы не блокируем пользовательский интерфейс.

NSData *movieReferenceData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; 

Наконец, после каждой отдельной загрузки изображения и после того, как мы закончили с нашим объектом NSData для этого изображения, мы вызываем:

[sharedCache removeAllCachedResponses]; 

Наше пиковое поведение при распределении памяти упало до очень удобной горстки мегабайт и перестало расти.

В этом случае вы видите именно то, что должны увидеть. -[NSMutableData appendData:] увеличивает размер своего внутреннего буфера для хранения новых данных. С NSMutableData всегда находится в памяти, это вызывает соответствующее увеличение использования памяти. Чего ты ожидал?

Если конечный пункт назначения для этих изображений находится на диске, попробуйте использовать NSOutputStream вместо NSMutableData, Если вы хотите отобразить изображение, вы можете создать UIImage указывая на файл, когда вы закончите.

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