Постоянно растущее выделение памяти при загрузке изображений через 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
указывая на файл, когда вы закончите.