ios7 - usa xcassets sin imageNamed para evitar problemas de memoria?
memory-leaks (1)
ACTUALIZACIÓN: el desalojo de caché funciona con multas (al menos desde iOS 8.3).
Decidí ir con los "nuevos Images.xcassets" de Apple, también. Las cosas empezaron a ir mal, cuando tenía aproximadamente 350 mb de imágenes en la aplicación y la aplicación se bloqueaba constantemente (en un Retina iPad; probablemente debido al tamaño de las imágenes cargadas).
He escrito una aplicación de prueba muy simple en la que cargo las imágenes en tres tipos diferentes (viendo el generador de perfiles):
imageNamed:
cargado desde un activo: las imágenes nunca se publican y la aplicación se bloquea (para mí, podría cargar 400 imágenes, pero realmente depende del tamaño de la imagen)imageNamed:
(incluido convencionalmente en el proyecto): el uso de memoria es alto y de vez en cuando (> 400 imágenes) veo una llamada adidReceiveMemoryWarning:
pero la aplicación funciona bien.imageWithContentsOfFile([[NSBundle mainBundle] pathForResource:...)
: el uso de la memoria es muy bajo (<20 mb) porque las imágenes solo se cargan una vez a la vez.
Realmente no culparía al almacenamiento en caché de imageNamed:
método de todo como almacenamiento en caché es una buena idea si tiene que mostrar sus imágenes una y otra vez, pero es un poco triste que Apple no lo haya implementado para los activos (o no lo hizo). documentar que no está implementado). En mi caso de uso, imageWithData
sin almacenamiento en caché porque el usuario no volverá a ver las imágenes.
Como mi aplicación es casi definitiva y realmente me gusta el uso del mecanismo de carga para encontrar la imagen correcta automáticamente, decidí envolver el uso:
- Quité el conjunto images.xcasset del proyecto-target-copy-phase y agregué todas las imágenes "otra vez" al proyecto y al copy-phase (simplemente agregue la carpeta de nivel superior de Images.xcassets directamente y asegúrese de que la casilla de verificación "Agregar To Target xxx "está marcado y" Crear grupos para cualquier carpeta agregada "(no me preocupé por los inútiles archivos Contents.json).
- Durante la primera compilación, compruebe si hay nuevas advertencias si varias imágenes tienen el mismo nombre (y cámbielas de forma consistente).
- Para el ícono de la aplicación y las Imágenes de inicio, establezca "No usar el catálogo de recursos" en project-target-general y haga referencia allí manualmente.
- He escrito un script de shell para generar un modelo json a partir de todos los archivos Contents.json (para que la información la utilice Apple en su código de acceso a los activos)
Guión:
cd projectFolderWithImageAsset
echo "{/"assets/": [" > a.json
find Images.xcassets/ -name /*.json | while read jsonfile; do
tmppath=${jsonfile%.imageset/*}
assetname=${tmppath##*/}
echo "{/"assetname/":/"${assetname}/",/"content/":" >> a.json
cat $jsonfile >> a.json;
echo ''},'' >>a.json
done
echo '']}'' >>a.json
- Elimine el último "," coma de la salida de json ya que no me molesté en hacerlo manualmente aquí.
- He usado la siguiente aplicación para generar el código de acceso de json-model: https://itunes.apple.com/de/app/json-accelerator/id511324989?mt=12 (actualmente gratis) con el prefijo IMGA
- He escrito una buena categoría usando el método swizzling para no cambiar el código en ejecución (y espero eliminar mi código muy pronto):
(Implementación no completa para todos los dispositivos y mecanismos de reserva!)
#import "UIImage+Extension.h"
#import <objc/objc-runtime.h>
#import "IMGADataModels.h"
@implementation UIImage (UIImage_Extension)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
Method imageNamed = class_getClassMethod(class, @selector(imageNamed:));
Method imageNamedCustom = class_getClassMethod(class, @selector(imageNamedCustom:));
method_exchangeImplementations(imageNamed, imageNamedCustom);
});
}
+ (IMGABaseClass*)model {
static NSString * const jsonFile = @"a";
static IMGABaseClass *baseClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *fileFilePath = [[NSBundle mainBundle] pathForResource:jsonFile ofType:@"json"];
NSData* myData = [NSData dataWithContentsOfFile:fileFilePath];
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:myData
options:kNilOptions error:&error];
if (error != nil) {
ErrorLog(@"Could not load file %@. The App will be totally broken!!!", jsonFile);
} else {
baseClass = [[IMGABaseClass alloc] initWithDictionary:result];
}
});
return baseClass;
}
+ (UIImage *)imageNamedCustom:(NSString *)name{
NSString *imageFileName = nil;
IMGAContent *imgContent = nil;
CGFloat scale = 2;
for (IMGAAssets *asset in [[self model] assets]) {
if ([name isEqualToString: [asset assetname]]) {
imgContent = [asset content];
break;
}
}
if (!imgContent) {
ErrorLog(@"No image named %@ found", name);
}
if (is4InchScreen) {
for (IMGAImages *image in [imgContent images]) {
if ([@"retina4" isEqualToString:[image subtype]]) {
imageFileName = [image filename];
break;
}
}
} else {
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ) {
for (IMGAImages *image in [imgContent images]) {
if ([@"iphone" isEqualToString:[image idiom]] && ![@"retina4" isEqualToString:[image subtype]]) {
imageFileName = [image filename];
break;
}
}
} else {
if (isRetinaScreen) {
for (IMGAImages *image in [imgContent images]) {
if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
imageFileName = [image filename];
break;
}
}
} else {
for (IMGAImages *image in [imgContent images]) {
if ([@"universal" isEqualToString:[image idiom]] && [@"1x" isEqualToString:[image scale]]) {
imageFileName = [image filename];
if (nil == imageFileName) {
// fallback to 2x version for iPad unretina
for (IMGAImages *image in [imgContent images]) {
if ([@"universal" isEqualToString:[image idiom]] && [@"2x" isEqualToString:[image scale]]) {
imageFileName = [image filename];
break;
}
}
} else {
scale = 1;
break;
}
}
}
}
}
}
if (!imageFileName) {
ErrorLog(@"No image file name found for named image %@", name);
}
NSString *imageName = [[NSBundle mainBundle] pathForResource:imageFileName ofType:@""];
NSData *imgData = [NSData dataWithContentsOfFile:imageName];
if (!imgData) {
ErrorLog(@"No image file found for named image %@", name);
}
UIImage *image = [UIImage imageWithData:imgData scale:scale];
DebugVerboseLog(@"%@", imageFileName);
return image;
}
@end
de acuerdo con la documentación de Apple, se recomienda usar xcassets para aplicaciones iOS7 y hacer referencia a esas imágenes sobre imageNamed.
Pero que yo sepa, siempre hubo problemas con imageNamed y la memoria.
Así que hice una breve aplicación de prueba, haciendo referencia a las imágenes del catálogo de xcassets con imageNamed e inicié el generador de perfiles ... el resultado fue el esperado. Una vez que la memoria asignada no se liberó de nuevo, incluso después de que quité el ImageView de superview y lo puse a nil.
Actualmente estoy trabajando en una aplicación de iPad con muchas imágenes grandes y este extraño comportamiento de imageView lleva a advertencias de memoria.
Pero en mis pruebas no pude acceder a las imágenes de xcassets sobre imageWithContentsOfFile.
Entonces, ¿cuál es el mejor enfoque para trabajar con imágenes grandes en iOS7? ¿Hay alguna forma de acceder a las imágenes desde el catálogo de xcassets de otra manera (con más rendimiento)? ¿O no debería usar xcassets para poder trabajar con imageWithContentsOfFile?
¡Gracias por sus respuestas!