Dissiper l'image UIImageNamed: FUD

117

Edit Février 2014: Notez que cette question date d'iOS 2.0! Les exigences et la gestion des images ont beaucoup évolué depuis. Retina agrandit les images et leur chargement un peu plus complexe. Avec la prise en charge intégrée des images iPad et Retina, vous devez certainement utiliser ImageNamed dans votre code .

Je vois beaucoup de gens dire que imageNamedc'est mauvais, mais un nombre égal de personnes dire que la performance est bonne - en particulier lors du rendu UITableView. Voir cette question SO par exemple ou cet article sur iPhoneDeveloperTips.com

UIImageLa imageNamedméthode utilisée pour fuir était donc mieux évitée, mais 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 faire un effort supplémentaire et le faire moi-même. Mon actuelle compréhension de base est qu'il est simple NSMutableDictionaryde UIImagesréférencé par nom de fichier. Il devient plus gros et lorsque la mémoire est épuisée, il devient beaucoup plus petit.

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

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

Rog
la source
2
moi aussi. On dirait que SO n'est pas aussi plein de génies que je le pensais.
Rog
On dirait que vous avez passé du temps à enquêter sur cela. Avez-vous fait une expérimentation qui montrerait un impact négatif de UIImage imageNamed avec la dernière version cocoa-touch? Créez un UITableView et ajoutez des lots (plusieurs milliers) de lignes et voyez si les performances diminuent lorsque vous affichez une image différente dans chaque ligne. Peut-être que les gens pourront alors commenter vos découvertes.
stefanB
En fait, je n'ai pas fait beaucoup d'expériences. J'utilise imageNamed et bien que je n'ai pas encore optimisé cette application en mémoire, je n'ai rien que je puisse pointer directement sur imageNamed comme un porc de mémoire. J'ai signalé quelques-uns des articles de blog se plaignant de imagedNamed ici et j'ai posé une question similaire sur les tableaux Apple qui ont plus d'action. Je republierai un résumé et un lien ici quand j'aurai le temps et sentirai qu'il a eu les réponses qu'il va obtenir.
Rog

Réponses:

85

tldr: ImagedNamed est très bien. Il gère bien la mémoire. Utilisez-le et arrêtez 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. Retina agrandit les images et leur chargement un peu plus complexe. Avec la prise en charge intégrée des images iPad et Retina, vous devez certainement utiliser ImageNamed dans votre code. Maintenant, pour la postérité:

Le fil sœur sur les forums Apple Dev a reçu un meilleur trafic. Plus précisément, Rincevent 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. Dans le même temps + imageNamed: a été 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û.

tout en avertissant 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: fait est de décoder les données d'image du fichier source, ce qui gonfle presque toujours considérablement la taille des données (par exemple, un fichier PNG de la taille d'un écran peut consommer quelques dizaines de Ko lorsqu'il est compressé, mais consomme plus d'un demi-Mo décompressé - largeur * hauteur * 4). Par contraste + imageWithContentsOfFile: décompressera cette image à chaque fois que les données d'image sont nécessaires. Comme vous pouvez l'imaginer, si vous n'avez besoin des données d'image qu'une seule fois, vous n'avez rien gagné ici, sauf d'avoir une version mise 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, même si 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 fait du cache basé sur le 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 augmentera de manière dynamique à mesure que vous demanderez plus d'images via + imageNommé:. Sur iPhone OS 2.x, un bogue empêche la réduction du cache lorsqu'un avertissement de mémoire est reçu.

et

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

Alors, voilà. imageNamed: ne brisera pas vos fenêtres ni ne tuera 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 qui soit aussi facile à utiliser - par conséquent, les gens en abusent et s'énervent quand il fait simplement son travail

J'ai ajouté une catégorie à UIImage pour résoudre ce problème:

// 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 a également inclus un exemple de code pour créer votre propre version optimisée. Je ne vois pas que cela vaut la peine de le maintenir 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.

Rog
la source
Il semble que sur iOS11, [UIImage imageNamed:] ne supprime pas les images du cache si les images proviennent du catalogue xcasset. Cela conduit à des plantages dus à un manque de mémoire.
Juraj Antas
5

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 minces que je pouvais les obtenir en ce qui concerne la gestion des mémoires, mais qui tombaient toujours inexplicablement en panne en raison du manque de mémoire. Lorsque j'ai arrêté d'utiliser imageNamed pour charger les images, les deux applications sont devenues considérablement plus stables.

J'avoue que les deux applications ont chargé des images un peu volumineuses, mais rien de tout à fait 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 à la même image. Dans le second, j'ai construit une classe de mise en cache très simple faisant exactement ce que vous avez mentionné: conserver UIImages dans un NSMutableDictionary, puis vider 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 la mise en cache UIImage

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

Bdebeez
la source
J'ai vu exactement le même comportement et la lecture du fichier l'a directement résolu. De plus, cela se produit lorsque j'essaye de charger une très grande image - 11 456 x 3226 px, 15 Mo. Ma théorie est que le système manque de mémoire à mi-chemin de l'opération de cache ImageNamed. Et il n'y a pas de gestion intégrée pour ce cas.
Dogweather