Bilder Cache

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Hi,

    ich habe eine Methode geschrieben die überprüft ob eine Datei bereits im Documents Ordner der Sandbox exestiert und wenn nicht wird das Bild vom Web geladen und dort abgelegt.

    Die Methode sieht wie folgt aus:

    Quellcode

    1. -(UIImage *) getImageFromDeviceWithFilename:(NSString *)fileName {
    2. NSArray *sysPaths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES );
    3. NSString *docDirectory = [sysPaths objectAtIndex:0];
    4. NSString *filePath = [NSString stringWithFormat:@"%@/%@", docDirectory, fileName];
    5. if ([[UIImage alloc] initWithContentsOfFile:filePath]) {
    6. return [[UIImage alloc] initWithContentsOfFile:filePath];
    7. }else{
    8. NSString *suffix = @".jpg";
    9. if (self.isRetina) {
    10. suffix = @"@2x.png";
    11. }
    12. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    13. NSString *documentsDirectory = [paths objectAtIndex:0];
    14. NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", BASE_URL, [fileName stringByReplacingOccurrencesOfString:@".png" withString:suffix]]];
    15. [self saveImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]] withFileName:fileName ofType:@"jpg" inDirectory:documentsDirectory];
    16. NSData * imageData = [[NSData alloc] initWithContentsOfURL:url];
    17. return [UIImage imageWithData:imageData];
    18. }
    19. }
    Alles anzeigen



    Das Problem ist das ich diese Methode verwende um Bilder anzuzeigen in einem UICollectionView. Da diese Methode natürlich jedes mal aufgerufen wird sobald die Zelle wieder erscheint, ruckelt es teilweise und ich wollte fragen was man hier noch optimieren kann.

    Eine Lösung wie SDWebImageCache ist keine Option da ich das Bild auch nach einem Neutstart der App komplett offline haben möchte.
  • du must halt das Laden asynchron machen. Ist das Bild nicht da, gibst du erstmal ein Dummy-Bild zurück und startestn den asynchronen Ladevorgang. Ist das Bild geladen must du dem CollectionView eine Notification schicken, dass es die entsprechende Zelle refreshen soll.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Mhhh also ich hab jetzt beides versucht einmal normal mit SDWeb und einmal über nen NSSet.
    Und es ruckelt immer noch. Und es liegt 100 pro am Bild weil wenn ich das Bild rausnehme bzw. nicht setze ist es so schnell wie normal.

    Eine Zelle vom UICollectionView ist bei mir so groß wie ein screen (iPad) - navbar etc. und darin ist ein komplettes Bild. Liegt es vlt. daran das, dass Bild zu groß ist? Kann ich mir aber eigentlich nicht vorstellen :/
  • Ich denke es liegt daran das das Bild zu groß ist. Da wirst du wohl erstmal ein kleines laden müssen, dass großskaliert zeigen (Also mit geringer Qualität) und dann im Hintergrund das große nachladen und austauschen wenn es denn geladen ist bevor weiter gescrollt wurde. Das ist alles nicht so trivial
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Bin erst gestern in das selbe Problem gelaufen. Die Bilder sind einfach zu groß. Falls ein Bild 400kb hat, muss die Größe doch auch dementsprechend sein. Wird es auch so groß angezeigt oder runter skaliert ? Bilder verkleinern schien mir bei 6 Bilder pro screen die einzige Möglichkeit zu sein.
    _____________________________
    Alle Angaben ohne Gewähr :)

    On the internet you can be anything you want. It's strange that so many people choose to be stupid.


    Superbientem animus prosternet
  • Alex schrieb:

    Bin erst gestern in das selbe Problem gelaufen. Die Bilder sind einfach zu groß. Falls ein Bild 400kb hat, muss die Größe doch auch dementsprechend sein. Wird es auch so groß angezeigt oder runter skaliert ?
    Bilder verkleinern schien mir bei 6 Bilder pro screen die einzige Möglichkeit zu sein.

    Mhh komisch. Ich mein wir beide sind doch bestmmt nicht die einzigen die hochauflösende Bilder anzeigen lassen wollen. Da muss es doch eine performate Lösung zu geben. Habe ja gerade deswegen mich für einen CollectionView entschieden statt einem Simplen ScrollView :D

    P.S: Mein Bild ist 2048 × 1536

    Liebe Grüße
  • Ich komm jetzt nicht dahinter was Du mit CollectionView machen möchtest. Eine CollectionView sind doch viele kleine views neben und untereinander.
    Wenn es so große Bilder sind, haben CollectionViews ja eh keinen Sinn mehr. Da würde ich einfach nach jedem Bild im Hintergrund aufbauen. Sonst gehts einfach nicht.
    _____________________________
    Alle Angaben ohne Gewähr :)

    On the internet you can be anything you want. It's strange that so many people choose to be stupid.


    Superbientem animus prosternet
  • Cachen ist tatsächlich das Zauberwort

    Das was wirklich Zeit kostet sind Datenzugriffe - RAM ist schon lahm, aber Platte (beziehungsweise SSD) ist noch einmal deutlich langsamer (danach kommt dann noch mal Daten aus dem Netzwerk).
    Wenn man Zeit sparen will, sollte man so etwas also tunlichst meiden:

    Quellcode

    1. if ([[UIImage alloc] initWithContentsOfFile:filePath]) {
    2. return [[UIImage alloc] initWithContentsOfFile:filePath];

    Du liest ein Bild, und wenn das klappt, wiederholst du das gleich noch einmal - bei "imageNamed" kümmert sich das System ja noch darum, dass man nicht ständig auf die SSD zugreifen muss, aber ich bezweifle, dass das bei "initWithContentsOfFile" auch der Fall ist.
  • Wenn die UIImageView das Bild skaliert anzeigen lassen muss kann es auch lahm werden bei vielen Bildern.

    Ich würde es so machen:
    - Cache im Arbeitsspeicher für die Bilder anlegen (z.B. NSCache)
    - beim Zuweisen in cellForRow... das Bild anhand deines Identifiers für die Zelle aus dem Cache laden und zuweisen.
    - Ist das Ergebnis dieser Zuweisung (also das Bild der ImageView) nun nil, weil es noch nicht im Cache war: --> Weiteres Vorgehen Async/im Background starten:
    - Laden von Disk wenn vorhanden, wenn nicht Laden aus dem Netz
    - Falls aus dem Netz geladen, Bild (weiterhin im Backgroundthread) auf die anzuzeigende Größe skalieren, dann speichern auf Disk
    - Wechel in den Mainthread:
    - Falls die Zelle immer noch den selben IndexPath representiert (wurde noch nicht durch schnelles scrollen reused bevor das Bild fertig geladen wurde) --> Bild zuweisen

    Wenn du das per GCD machst: Eben noch testen ob man die High Priority Queue verwenden kann ohne dass es ruckelt, sonst eben mit niedrigerer Priority testen.
  • Tobse001 schrieb:

    Wenn die UIImageView das Bild skaliert anzeigen lassen muss kann es auch lahm werden bei vielen Bildern.

    Ich würde es so machen:
    - Cache im Arbeitsspeicher für die Bilder anlegen (z.B. NSCache)
    - beim Zuweisen in cellForRow... das Bild anhand deines Identifiers für die Zelle aus dem Cache laden und zuweisen.
    - Ist das Ergebnis dieser Zuweisung (also das Bild der ImageView) nun nil, weil es noch nicht im Cache war: --> Weiteres Vorgehen Async/im Background starten:
    - Laden von Disk wenn vorhanden, wenn nicht Laden aus dem Netz
    - Falls aus dem Netz geladen, Bild (weiterhin im Backgroundthread) auf die anzuzeigende Größe skalieren, dann speichern auf Disk
    - Wechel in den Mainthread:
    - Falls die Zelle immer noch den selben IndexPath representiert (wurde noch nicht durch schnelles scrollen reused bevor das Bild fertig geladen wurde) --> Bild zuweisen

    Wenn du das per GCD machst: Eben noch testen ob man die High Priority Queue verwenden kann ohne dass es ruckelt, sonst eben mit niedrigerer Priority testen.


    Quellcode

    1. NSString *imageName = [[self.photos objectAtIndex:indexPath.row] objectForKey:@"fullimage"];
    2. UIImage *image = [imageCache objectForKey:imageName];
    3. if(image){
    4. cell.imageView.image = image;
    5. }
    6. else{
    7. cell.imageView.image = nil;
    8. dispatch_queue_t downloadQueue = dispatch_queue_create("image downloader", NULL);
    9. dispatch_async(downloadQueue, ^{
    10. NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[self.photos objectAtIndex:indexPath.row] objectForKey:@"fullimage"]]];
    11. UIImage *image = [UIImage imageWithData:data];
    12. dispatch_async(dispatch_get_main_queue(), ^{
    13. cell.imageView.image = image;
    14. });
    15. [imageCache setObject:image forKey:imageName];
    16. });
    17. }
    Alles anzeigen


    er greift auf jedenfall auf das element im chache jetzt zu aber ruckeln tut es immer noch :/ mach da irgendwas falsch mit dem cache
  • Wie gesagt. Als erstes ist das mit der CollectionView schonmal der falsche Ansatz. Wenn das Bild Screenfüllend ist, dann solltest du einen ganz einfachen Scrollview nehmen und in diesem kannst du dann z.B. immer +-5 Bilder vorhalten. Das Caching ist dann halt etwas komplexer aber wahrscheinlich gibt es dafür auch irgendwo fertigen Code im Inet. Ich habe mir das mal selber geschrieben. Hat mich interessiert.
    Die zweite Variante ist halt, das du jedes Bild zweimal hast. Einmal als kleines und einmal als großes. Anzeigen tust du erstmal das kleine (mit den wenig Pixeln und schlechter Qualität) und stößt aber beim Anzeigen des Kleinen gleich die Laderoutine für das große an. Sobald das geladen wurde ersetzt du das kleine. So macht es Apple übrigens auch bei den Photos. Du must halt nur beim weiterscrollen das Laden des großen Bildes abbrechen und das nächste Starten. Sowas geht aber super über eine Queue z.B.

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Also wenn es damit ruckelt, kann es ja an sich nicht mehr am Laden liegen, das passiert ja schon im Hintergrund.
    Wenn das zu lahm ist, sollten die Bilder nur verzögert auftauchen und nicht das Scrollen an sich ruckeln.

    Somit muss es an sich am Anzeigen der Bilddaten liegen.
    Wenn die Bilder nicht genau so groß sind wie deine ImageView, muss die Imageview vor dem Anzeigen ja das Bild auf die neue Größe umrechnen.
    Dies geschieht dann natürlich blockierend im Mainthread.

    Liegen die Bilddaten denn genau in der richtigen Größe vor?
    Wenn nicht, im Hintergrundthread nicht nur herunterladen, sondern auch noch auf die richtige Größe skalieren (pixelgenau auf die der ImageView) bevor du es in den Cache speicherst und der ImageView zuweist.

    Ansonsten sollte das schon funktionieren, ich hab mit ALAsset's fullScreenImage (Photo Library) Bildern kein Problem flüssiges Scrollen zu implementieren.
    Evtl. hast du ja noch andere Stellen die den Spaß ausbremsen in deiner DataSource oder was auch sonst immer noch nebenher passieren mag.
    Hast du mal mit Instruments geschaut, welche Aufrufe am meisten Zeit in Anspruch nehmen?

    @Thallius: Warum sollte man dafür keine Table- oder CollectionViews verwenden?
    Man erfindet damit doch nur das Rad neu.
    Ich hab mit Vollbild-Collectionviews bisher keine negativen Erfahrungen gemacht was die Performance angeht.
    Zusammen mit pagingEnabled ist so eine Lösung zudem in ein paar Minuten zusammengebaut.