web-dev-qa-db-fra.com

Dissipation de l'image UIImageNom: FUD

Edit février 2014: Notez que cette question date d'iOS 2.0! Les exigences et la gestion des images ont beaucoup évolué depuis. La rétine agrandit les images et les charge légèrement plus complexes. Avec la prise en charge intégrée des images iPad et rétine, , vous devez certainement utiliser ImageNamed dans votre code .

Je vois beaucoup de gens dire que imageNamed est mauvais mais un nombre égal de personnes disant que les performances sont bonnes - en particulier lors du rendu de UITableViews. Voir cette SO question par exemple ou cet article sur iPhoneDeveloperTips.com

La méthode UIImage de imageNamed était utilisée pour fuir, il était donc préférable de l'éviter, mais elle a été corrigée dans les versions récentes. J'aimerais mieux comprendre l'algorithme de mise en cache afin de prendre une décision raisonnée sur l'endroit où je peux faire confiance au système pour mettre en cache mes images et où je dois aller plus loin et le faire moi-même. Ma compréhension de base actuelle est que c'est un simple NSMutableDictionary de UIImages référencé par nom de fichier. Il s'agrandit et lorsque la mémoire s'épuise, il devient beaucoup plus petit.

Par exemple, quelqu'un sait-il avec certitude que le cache d'image derrière imageNamed ne répond pas à didReceiveMemoryWarning? Il semble peu probable que Apple ne fasse pas cela.

Si vous avez un aperçu de l'algorithme de mise en cache, veuillez le poster ici.

117
Rog

tldr: ImagedNamed est très bien. Il gère bien la mémoire. Utilisez-le et cessez de vous inquiéter.

Edit Nov 2012 : Notez que cette question date d'iOS 2.0! Les exigences et la gestion des images ont beaucoup évolué depuis lors. La rétine agrandit les images et les charge légèrement plus complexes. Avec la prise en charge intégrée des images iPad et rétine, vous devez certainement utiliser ImageNamed dans votre code. Maintenant, pour l'amour de la postérité:

Le thread sœur sur les Apple Dev Forums a reçu un meilleur trafic. Plus précisément Rincewind a ajouté une certaine autorité.

Il y a des problèmes dans iPhone OS 2.x où le cache imageNamed: ne serait pas effacé, même après un avertissement de mémoire. En même temps, + imageNamed: a beaucoup utilisé non pas pour le cache, mais pour la commodité, ce qui a probablement amplifié le problème plus qu'il n'aurait dû l'être.

tout en prévenant que

Sur le front de la vitesse, il y a un malentendu général sur ce qui se passe. La plus grande chose que + imageNamed: décode les données d'image du fichier source, ce qui gonfle presque toujours de manière significative la taille des données (par exemple, un fichier PNG de taille d'écran peut consommer quelques dizaines de Ko lorsqu'il est compressé, mais consomme plus d'un demi-Mo décompressé - largeur * hauteur * 4). En revanche + imageWithContentsOfFile: décompressera cette image chaque fois que les données d'image seront nécessaires. Comme vous pouvez l'imaginer, si vous n'avez besoin que des données d'image une fois, vous n'avez rien gagné ici, sauf pour avoir une version en cache de l'image qui traîne, et probablement plus longtemps que vous n'en avez besoin. Cependant, si vous avez une grande image que vous devez souvent redessiner, il existe des alternatives, bien que celle que je recommanderais principalement est d'éviter de redessiner cette grande image :).

En ce qui concerne le comportement général du cache, il cache en fonction du nom de fichier (donc deux instances de + imageNamed: avec le même nom devraient entraîner des références aux mêmes données mises en cache) et le cache se développera dynamiquement lorsque vous demanderez plus d'images via + imageNamed:. Sur iPhone OS 2.x, un bogue empêche le cache d'être réduit lorsqu'un avertissement de mémoire est reçu.

et

Ma compréhension est que le cache + imageNamed: doit respecter les avertissements de mémoire sur iPhone OS 3.0. Testez-le lorsque vous en avez l'occasion et signalez des bogues si vous constatez que ce n'est pas le cas.

Alors voilà. imageNamed: ne brisera pas vos fenêtres et ne tuera pas vos enfants. C'est assez simple mais c'est un outil d'optimisation. Malheureusement, il est mal nommé et il n'y a pas d'équivalent aussi facile à utiliser - par conséquent, les gens en abusent et se fâchent quand il fait simplement son travail

J'ai ajouté une catégorie à UIImage pour corriger cela:

// 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]];
}

Rincevent a également inclus un exemple de code pour créer votre propre version optimisée. Je ne vois pas que cela vaut la peine d'être entretenu, mais ici, c'est pour être complet.

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);

Le compromis avec ce code est que l'image décodée utilise plus de mémoire mais le rendu est plus rapide.

86
Rog

D'après mon expérience, le cache d'image créé par imageNamed ne répond pas aux avertissements de mémoire. J'ai eu deux applications qui étaient aussi simples que possible pour la gestion des mem, mais qui se bloquaient toujours inexplicablement en raison du manque de mem. Lorsque j'ai cessé d'utiliser imageNamed pour charger les images, les deux applications sont devenues considérablement plus stables.

Je dois admettre que les deux applications ont chargé des images un peu grandes, mais rien qui serait totalement hors de l'ordinaire. Dans la première application, j'ai simplement ignoré la mise en cache car il était peu probable qu'un utilisateur revienne deux fois sur la même image. Dans la seconde, j'ai construit une classe de mise en cache vraiment simple en faisant exactement ce que vous avez mentionné - en gardant les UIImages dans un NSMutableDictionary puis en vidant son contenu si je recevais un avertissement de mémoire. Si imageNamed: devait mettre en cache comme ça, alors je n'aurais pas dû voir de mise à niveau des performances. Tout cela fonctionnait sur 2.2 - je ne sais pas s'il y a des implications 3.0 à ce sujet.

Vous pouvez trouver mon autre question sur ce problème à partir de ma première application ici: question StackOverflow sur le cache UIImage

Une autre remarque - InterfaceBuilder utilise imageNamed sous les couvertures. Quelque chose à garder à l'esprit si vous rencontrez ce problème.

5
Bdebeez