iphone - Disipando el UIImage imageNamed: FUD
caching uikit (2)
En mi experiencia, la memoria caché de imágenes creada por imageNamed no responde a las advertencias de memoria. He tenido dos aplicaciones que eran tan delgadas como podía conseguirlas en cuanto a la administración de la memoria, pero seguían inexplicablemente bloqueadas debido a la falta de memoria. Cuando dejé de usar imageNamed para cargar las imágenes, ambas aplicaciones se volvieron dramáticamente más estables.
Admitiré que ambas aplicaciones cargaron imágenes algo grandes, pero nada que fuera totalmente fuera de lo común. En la primera aplicación, me salté el almacenamiento en caché por completo porque era poco probable que un usuario volviera a la misma imagen dos veces. En el segundo, construí una clase de almacenamiento en caché muy simple haciendo exactamente lo que usted mencionó: mantener UIImages en un NSMutableDictionary y luego descargar su contenido si recibí una advertencia de memoria. Si imageNamed: caché así, entonces no debería haber visto ninguna actualización de rendimiento. Todo esto se estaba ejecutando en 2.2 - No sé si hay implicaciones de 3.0 en esto.
Puedes encontrar mi otra pregunta sobre este tema desde mi primera aplicación aquí: Pregunta de StackOverflow sobre el almacenamiento en caché de UIImage
Otra nota: InterfaceBuilder usa imageNamed debajo de las cubiertas. Algo a tener en cuenta si se encuentra con este problema.
Edite feb 2014: ¡ tenga en cuenta que esta pregunta data de iOS 2.0! Los requisitos de imagen y manejo han avanzado mucho desde entonces. Retina hace las imágenes más grandes y las carga un poco más complejas. Con el soporte incorporado para imágenes de iPad y retina, seguramente deberías usar ImageNamed en tu código .
Veo a mucha gente diciendo que imageNamed
es malo, pero hay un número igual de personas que dicen que el rendimiento es bueno, especialmente cuando se renderizan las UITableView
. Vea esta pregunta SO por ejemplo o este artículo en iPhoneDeveloperTips.com
UIImage
utilizó un método imageNamed
para filtrar, por lo que era mejor evitarlo, pero se ha solucionado en versiones recientes. Me gustaría entender mejor el algoritmo de almacenamiento en caché para tomar una decisión razonada sobre dónde puedo confiar en que el sistema almacenará en caché mis imágenes y dónde tengo que hacer un esfuerzo adicional y hacerlo yo mismo. Mi comprensión básica actual es que es un simple NSMutableDictionary
de UIImages
referencia por nombre de archivo. Se hace más grande y cuando la memoria se agota se vuelve mucho más pequeña.
Por ejemplo, ¿alguien sabe con certeza que la memoria caché de imágenes detrás de imageNamed
no responde a didReceiveMemoryWarning
? Parece poco probable que Apple no haga esto.
Si tiene alguna idea sobre el algoritmo de almacenamiento en caché, publíquelo aquí.
tldr: ImagedNamed está bien. Maneja bien la memoria. Úselo y deja de preocuparte.
Edición de noviembre de 2012 : ¡tenga en cuenta que esta pregunta data de iOS 2.0! Los requisitos de imagen y manejo han avanzado mucho desde entonces. Retina hace las imágenes más grandes y las carga un poco más complejas. Con el soporte incorporado para imágenes de iPad y retina, seguramente deberías usar ImageNamed en tu código. Ahora, por el bien de la posteridad:
El hilo hermano en los foros de Apple Dev recibió un tráfico mejor. Específicamente, Rincewind agregó algo de autoridad.
Hay problemas en el iPhone OS 2.x donde el caché imageNamed: no se borró, incluso después de una advertencia de memoria. Al mismo tiempo, + imageNamed: se ha utilizado mucho no para la memoria caché, sino para mayor comodidad, lo que probablemente ha magnificado el problema más de lo que debería haber sido.
mientras adviertes que
En el frente de la velocidad, hay un malentendido general de lo que está sucediendo. Lo más importante que + imageNamed: hace es decodificar los datos de imagen del archivo fuente, lo que casi siempre infla significativamente el tamaño de los datos (por ejemplo, un archivo PNG con tamaño de pantalla podría consumir unas pocas docenas de KB cuando está comprimido, pero consume más de medio MB descomprimido - ancho * alto * 4). Por contraste + imageWithContentsOfFile: descomprimirá esa imagen cada vez que se necesiten los datos de la imagen. Como puedes imaginar, si solo necesitas los datos de la imagen una vez, no has ganado nada aquí, excepto para tener una versión almacenada en caché de la imagen, y probablemente por más tiempo del que necesites. Sin embargo, si tiene una imagen grande que necesita volver a dibujar a menudo, existen alternativas, aunque la que recomiendo principalmente es evitar volver a dibujar esa imagen grande :).
Con respecto al comportamiento general de la memoria caché, la memoria caché se basa en el nombre de archivo (por lo que dos instancias de + imageNamed: con el mismo nombre deberían generar referencias a los mismos datos en caché) y la memoria crecerá dinámicamente a medida que solicite más imágenes a través de + imageNamed :. En iPhone OS 2.xa error evita que la memoria caché se reduzca cuando se recibe una advertencia de memoria.
y
Mi entendimiento es que el + imageNamed: caché debe respetar las advertencias de memoria en iPhone OS 3.0. Pruébelo cuando tenga la oportunidad e informe de errores si encuentra que este no es el caso.
Entonces, ahí lo tienes. imageNamed: no destruirá tus ventanas ni asesinará a tus hijos. Es bastante simple pero es una herramienta de optimización. Lamentablemente está mal nombrado y no existe un equivalente que sea tan fácil de usar, por lo tanto, la gente lo usa en exceso y se molesta cuando simplemente hace su trabajo.
Agregué una categoría a UIImage para arreglar eso:
// 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 también incluyó algún código de ejemplo para construir su propia versión optimizada. No puedo ver que valga la pena el maintentace, pero aquí está para completar.
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);
La compensación con este código es que la imagen decodificada usa más memoria, pero la representación es más rápida.