2009-05-29 4 views
116

Редактировать Feb 2014: Обратите внимание, что этот вопрос датируется iOS 2.0! Требования к изображениям и обработка с тех пор значительно изменились. Retina делает изображения более крупными и загружает их немного сложнее. Со встроенной поддержкой IPad и изображений сетчатки, , вы обязательно должны использовать ImageNamed в своем коде.Dispelling the UIImage imageNamed: FUD

Я вижу много людей, говорящих, что imageNamed плохо, но равное количество людей, говорящих, что производительность хорошая - особенно при рендеринге UITableView. См this SO question, например или this article на iPhoneDeveloperTips.com

UIImage «s imageNamed метод, используемый для утечки, так что лучше всего избежать, но была зафиксирована в последних версиях. Я хотел бы лучше понять алгоритм кеширования, чтобы обоснованное решение о том, где я могу доверять системе, чтобы кэшировать мои изображения и где мне нужно пройти лишнюю милю и сделать это самостоятельно. Мое настоящее основное понимание заключается в том, что это простой NSMutableDictionaryUIImages, на который ссылается имя файла. Он становится больше, и когда память заканчивается, он становится намного меньше.

Например, кто-нибудь знает наверняка, что кэш изображения за imageNamed не отвечает на didReceiveMemoryWarning? Кажется маловероятным, что Apple этого не сделает.

Если у вас есть понимание алгоритма кэширования, отправьте его здесь.

+1

Я в напряжении. :) – Kriem

+2

меня тоже. Кажется, что SO не так полна гениев, как я думал. –

+0

Похоже, вы провели некоторое время, исследуя это. Провели ли вы какие-либо эксперименты, которые продемонстрировали бы какое-либо негативное влияние UIImage imageNamed на последнюю версию какао-touch? Создайте UITableView и добавьте много (много тысяч) строк и посмотрите, снижается ли производительность при отображении разных изображений в каждой строке. Может быть, тогда люди могут прокомментировать ваши выводы. – stefanB

ответ

86

tldr: ImagedNamed в порядке. Он хорошо справляется с памятью. Используйте его и перестаньте беспокоиться.

Редактировать Ноябрь 2012 г.: Обратите внимание, что этот вопрос датируется iOS 2.0! С тех пор требования к изображениям и обработка сильно изменились. Retina делает изображения более крупными и загружает их немного сложнее. Благодаря встроенной поддержке изображений iPad и сетчатки, вы обязательно должны использовать ImageNamed в своем коде. Теперь, ради потомства:

sister thread на форумах Apple Dev получили некоторый лучший трафик. В частности, Rincewind добавил некоторые полномочия.

В iPhone OS 2.x есть проблемы, в которых кеш изображенияNamed: не будет очищен даже после предупреждения о памяти. В то же время + imageNamed: получил большую пользу не для кеша, а для удобства, что, вероятно, увеличило проблему больше, чем должно было быть.

одновременно предупреждая, что

На передней скорости, есть общее непонимание того, что происходит. Самая большая вещь, которую + imageNamed: делает, декодирует данные изображения из исходного файла, который почти всегда значительно раздувает размер данных (например, размер PNG-файла с экраном может потреблять несколько десятков КБ при сжатии, но потребляет более половины МБ декомпрессированная - ширина * высота * 4). В отличие от + imageWithContentsOfFile: будет декомпрессировать это изображение каждый раз, когда нужны данные изображения. Как вы можете себе представить, если вам нужны только данные изображения один раз, вы ничего здесь не выиграли, за исключением того, что у вас есть кешированная версия изображения, висящая вокруг, и, вероятно, дольше, чем вам это нужно. Однако, если у вас есть большое изображение, которое вам нужно часто перерисовывать, тогда есть альтернативы, хотя я бы рекомендовал в первую очередь избегать перерисовки этого большого изображения :).

Что касается общего поведения кеша, он выполняет кэширование на основе имени файла (поэтому два экземпляра + imageNamed: с тем же именем должны приводить ссылки на одни и те же кэшированные данные), и кеш будет динамически расти, так как вы запросить дополнительные изображения с помощью + imageNamed :. В iPhone OS 2.x ошибка предотвращает сжатие кеша при получении предупреждения о памяти.

и

Я понимаю, что + imageNamed: кэш должен соблюдать предупреждения памяти на iPhone OS 3.0. Проверьте это, когда вы получите шанс и сообщите об ошибках, если обнаружите, что это не так.

Итак, у вас оно есть. imageNamed: не будет разбивать ваши окна или убивать ваших детей. Это довольно просто, но это инструмент оптимизации. К сожалению, он плохо назван и нет equivaluent что проста в использовании - поэтому люди злоупотребляют его и расстраиваться, когда он просто делает свою работу

Я добавил категорию в UIImage, чтобы исправить это:

// header omitted 
// Before you waste time editing this, please remember that a semi colon at the end of a method definition is valid and a matter of style. 
+ (UIImage*)imageFromMainBundleFile:(NSString*)aFileName; { 
    NSString* bundlePath = [[NSBundle mainBundle] bundlePath]; 
    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", bundlePath,aFileName]]; 
} 

Rincewind также включил некоторый пример кода для создания собственной оптимизированной версии. Я не вижу, что это стоит maintentace, но здесь для полноты.

CGImageRef originalImage = uiImage.CGImage; 
CFDataRef imageData = CGDataProviderCopyData(
    CGImageGetDataProvider(originalImage)); 
CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData(imageData); 
CFRelease(imageData); 
CGImageRef image = CGImageCreate(
    CGImageGetWidth(originalImage), 
    CGImageGetHeight(originalImage), 
    CGImageGetBitsPerComponent(originalImage), 
    CGImageGetBitsPerPixel(originalImage), 
    CGImageGetBytesPerRow(originalImage), 
    CGImageGetColorSpace(originalImage), 
    CGImageGetBitmapInfo(originalImage), 
    imageDataProvider, 
    CGImageGetDecode(originalImage), 
    CGImageGetShouldInterpolate(originalImage), 
    CGImageGetRenderingIntent(originalImage)); 
CGDataProviderRelease(imageDataProvider); 
UIImage *decompressedImage = [UIImage imageWithCGImage:image]; 
CGImageRelease(image); 

Компромисс с этим кодом заключается в том, что декодированное изображение использует больше памяти, но рендеринг выполняется быстрее.

+0

Кажется, что на iOS11 [UIImage imageNamed:] не выдает изображения из кеша, если изображения поступают из каталога xcasset. Это приводит к сбоям из-за нехватки памяти. –

5

По моему опыту, кэш изображений, созданный imageNamed, не отвечает на предупреждения о памяти. У меня было два приложения, которые были такими же тонкими, насколько я мог довести их до управления mem, но все еще необъяснимо сбой из-за нехватки памяти. Когда я прекратил использовать imageNamed для загрузки изображений, оба приложения стали значительно более стабильными.

Допустим, что оба приложения загружали несколько больших изображений, но ничего, что было бы совершенно необычным. В первом приложении я просто пропустил кеширование, потому что вряд ли пользователь дважды вернется к тому же изображению. Во-вторых, я построил действительно простой класс кеширования, выполнив только то, что вы упомянули, - сохраняя UIImages в NSMutableDictionary, а затем очищая его содержимое, если я получил предупреждение о памяти. Если imageNamed: должны были кэшироваться, то я не должен был видеть повышение производительности. Все это работало на 2.2 - я не знаю, есть ли какие-либо 3.0 последствия для этого.

Вы можете найти мой другой вопрос вокруг этого вопроса от моего первого приложения здесь: StackOverflow question about UIImage cacheing

Еще одно замечание - InterfaceBuilder использует imageNamed под одеялом. Что-то нужно иметь в виду, если вы столкнулись с этой проблемой.

+0

Я видел то же самое поведение, и чтение из файла напрямую разрешило его. Кроме того, это происходит, когда я пытаюсь загрузить очень большое изображение - 11 456 x 3 226 px, 15 МБ. Моя теория заключается в том, что система исчерпывает память частично через операцию ImageNamed cache. И для этого случая нет обработки. – Dogweather