UIImage imageNamed: FUD entfernen

117

Bearbeiten Februar 2014: Beachten Sie, dass diese Frage von iOS 2.0 stammt! Die Bildanforderungen und die Handhabung haben sich seitdem stark weiterentwickelt. Die Netzhaut vergrößert Bilder und lädt sie etwas komplexer. Mit der integrierten Unterstützung für iPad- und Retina-Bilder sollten Sie ImageNamed auf jeden Fall in Ihrem Code verwenden .

Ich sehe viele Leute, die sagen, dass imageNamedes schlecht ist, aber ebenso viele Leute, die sagen, dass die Leistung gut ist - besonders beim Rendern von UITableViews. Siehe diese SO-Frage zum Beispiel oder diesen Artikel auf iPhoneDeveloperTips.com

UIImageDie imageNamedMethode, die zum Auslaufen verwendet wurde, wurde am besten vermieden, wurde jedoch in den letzten Versionen behoben. Ich möchte den Caching-Algorithmus besser verstehen, um eine begründete Entscheidung darüber zu treffen, wo ich dem System vertrauen kann, dass es meine Bilder zwischenspeichert, und wo ich die Extrameile gehen und es selbst tun muss. Mein aktuelles Grundverständnis ist , dass es ein einfache NSMutableDictionaryvon UIImagesdurch Dateinamen verwiesen wird . Es wird größer und wenn der Speicher knapp wird, wird es viel kleiner.

Weiß zum Beispiel jemand sicher, dass der dahinter liegende imageNamedBildcache nicht reagiert didReceiveMemoryWarning? Es ist unwahrscheinlich, dass Apple dies nicht tun würde.

Wenn Sie einen Einblick in den Caching-Algorithmus haben, posten Sie ihn bitte hier.

Rog
quelle
2
ich auch. Scheint, als wäre SO nicht so genial, wie ich dachte.
Rog
Scheint, als hätten Sie einige Zeit damit verbracht, dies zu untersuchen. Haben Sie Experimente durchgeführt, die negative Auswirkungen von UIImage imageNamed mit der neuesten Cocoa-Touch-Version zeigen würden? Erstellen Sie eine UITableView, fügen Sie viele (viele tausend) Zeilen hinzu und prüfen Sie, ob die Leistung abnimmt, wenn Sie in jeder Zeile ein anderes Bild anzeigen. Vielleicht können die Leute dann Ihre Ergebnisse kommentieren.
stefanB
Eigentlich habe ich nicht so viel experimentiert. Ich verwende imageNamed und obwohl ich diese App noch nicht speicheroptimiert habe, habe ich nichts, was ich direkt auf imageNamed als Speicherfresser verweisen kann. Ich habe auf einige der Blog-Beiträge hingewiesen, in denen ich mich über imagedNamed hier beschwert habe, und ich habe auf den Apple-Boards eine ähnliche Frage gestellt, die mehr Action hat. Ich werde eine Zusammenfassung und einen Link hier erneut veröffentlichen, wenn ich Zeit habe und das Gefühl habe, dass ich die Antworten erhalten habe, die es bekommen wird.
Rog

Antworten:

85

tldr: ImagedNamed ist in Ordnung. Es geht gut mit dem Gedächtnis um. Verwenden Sie es und hören Sie auf, sich Sorgen zu machen.

Edit Nov 2012 : Beachten Sie, dass diese Frage von iOS 2.0 stammt! Die Bildanforderungen und die Handhabung haben sich seitdem stark weiterentwickelt. Die Netzhaut vergrößert Bilder und lädt sie etwas komplexer. Mit der integrierten Unterstützung für iPad- und Retina-Bilder sollten Sie ImageNamed auf jeden Fall in Ihrem Code verwenden. Nun, der Nachwelt zuliebe:

Der Schwester-Thread in den Apple Dev-Foren erhielt etwas besseren Traffic. Insbesondere Rincewind fügte einige Autorität hinzu.

In iPhone OS 2.x gibt es Probleme, bei denen der imageNamed: -Cache auch nach einer Speicherwarnung nicht gelöscht wird. Zur gleichen Zeit + imageNamed: wurde nicht für den Cache, sondern für die Bequemlichkeit viel genutzt, was das Problem wahrscheinlich mehr vergrößert hat, als es hätte sein sollen.

während ich das warne

An der Geschwindigkeitsfront gibt es ein allgemeines Missverständnis darüber, was los ist. Das Größte, was + imageNamed: bewirkt, ist das Dekodieren der Bilddaten aus der Quelldatei, wodurch die Datengröße fast immer erheblich erhöht wird (z. B. verbraucht eine PNG-Datei in Bildschirmgröße beim Komprimieren möglicherweise einige Dutzend KB, verbraucht jedoch mehr als ein halbes MB dekomprimiert - Breite * Höhe * 4). Im Gegensatz dazu + imageWithContentsOfFile: Dekomprimiert dieses Bild jedes Mal, wenn die Bilddaten benötigt werden. Wie Sie sich vorstellen können, haben Sie hier nichts gewonnen, wenn Sie die Bilddaten nur einmal benötigen, außer dass eine zwischengespeicherte Version des Bildes herumhängt und wahrscheinlich länger als nötig. Wenn Sie jedoch ein großes Bild haben, das Sie häufig neu zeichnen müssen, gibt es Alternativen, obwohl ich in erster Linie empfehlen würde, das Neuzeichnen dieses großen Bildes zu vermeiden :).

In Bezug auf das allgemeine Verhalten des Caches wird der Cache basierend auf dem Dateinamen erstellt (daher sollten zwei Instanzen von + imageNamed: mit demselben Namen zu Verweisen auf dieselben zwischengespeicherten Daten führen), und der Cache wächst dynamisch, wenn Sie weitere Bilder über anfordern + imageNamed:. Unter iPhone OS 2.xa verhindert ein Fehler, dass der Cache verkleinert wird, wenn eine Speicherwarnung empfangen wird.

und

Nach meinem Verständnis sollte der Cache + imageNamed: Speicherwarnungen unter iPhone OS 3.0 berücksichtigen. Testen Sie es, wenn Sie eine Chance bekommen, und melden Sie Fehler, wenn Sie feststellen, dass dies nicht der Fall ist.

Also, da hast du es. imageNamed: Zerschmettert nicht Ihre Fenster und ermordet Ihre Kinder nicht. Es ist ziemlich einfach, aber es ist ein Optimierungswerkzeug. Leider ist es schlecht benannt und es gibt kein Äquivalent, das so einfach zu bedienen ist - daher wird es von den Leuten überbeansprucht und ist verärgert, wenn es einfach seinen Job macht

Ich habe UIImage eine Kategorie hinzugefügt, um dies zu beheben:

// 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 enthielt auch Beispielcode, um Ihre eigene optimierte Version zu erstellen. Ich kann nicht sehen, dass es die Wartung wert ist, aber hier ist es der Vollständigkeit halber.

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

Der Nachteil dieses Codes ist, dass das decodierte Bild mehr Speicher benötigt, das Rendern jedoch schneller ist.

Rog
quelle
Es scheint, dass unter iOS11 [UIImage imageNamed:] keine Bilder aus dem Cache entfernt, wenn Bilder aus dem xcasset-Katalog stammen. Dies führt zu Abstürzen aufgrund von Speichermangel.
Juraj Antas
5

Nach meiner Erfahrung reagiert der von imageNamed erstellte Bildcache nicht auf Speicherwarnungen. Ich hatte zwei Anwendungen, die so schlank waren, wie ich sie bis zur Mem-Verwaltung erreichen konnte, aber immer noch unerklärlich abstürzten, weil es an Mem mangelte. Als ich imageNamed nicht mehr zum Laden der Bilder verwendete, wurden beide Anwendungen dramatisch stabiler.

Ich gebe zu, dass beide Anwendungen etwas große Bilder geladen haben, aber nichts, was völlig ungewöhnlich wäre. In der ersten Anwendung habe ich das Caching nur ganz übersprungen, da es unwahrscheinlich war, dass ein Benutzer zweimal zum selben Bild zurückkehren würde. Im zweiten Schritt habe ich eine wirklich einfache Caching-Klasse erstellt, die genau das tut, was Sie erwähnt haben - UIImages in einem NSMutableDictionary zu speichern und dann den Inhalt zu löschen, wenn ich eine Speicherwarnung erhalten habe. Wenn imageNamed: so zwischengespeichert würde, hätte ich keine Leistungssteigerung sehen sollen. All dies lief auf 2.2 - ich weiß nicht, ob es irgendwelche 3.0-Implikationen dafür gibt.

Meine andere Frage zu diesem Problem finden Sie in meiner ersten App hier: StackOverflow-Frage zum UIImage-Cache

Ein weiterer Hinweis: InterfaceBuilder verwendet imageNamed unter der Decke. Denken Sie daran, wenn Sie auf dieses Problem stoßen.

Bdebeez
quelle
Ich sah genau das gleiche Verhalten und das Lesen aus der Datei löste es direkt auf. Es passiert auch, wenn ich versuche, ein sehr großes Bild zu laden - 11.456 x 3.226 px, 15 MB. Meine Theorie ist, dass dem System während des ImageNamed-Cache-Vorgangs der Arbeitsspeicher ausgeht. Und für diesen Fall ist keine Handhabung eingebaut.
Dogweather