5 Stimmen

Erhebliche Verzögerung beim asynchronen Laden von Bildern mit UIImage von einer URL

Ich versuche, eine iPad-App zu schreiben, die ein Bild von einer URL lädt. Ich verwende den folgenden Code zum Laden von Bildern:

    url = [NSURL URLWithString:theURLString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    img = [[UIImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];
    NSLog(@"Image reloaded");

All dieser Code wird zu einem NSOperationQueue als eine Operation hinzugefügt, so dass es asynchron geladen wird und nicht dazu führen, dass meine app zu sperren, wenn das Bild der Websever langsam ist. Ich fügte die NSLog-Zeile hinzu, damit ich in der Konsole sehen konnte, wann dieser Code ausgeführt wurde.

Ich habe immer wieder festgestellt, dass das Bild in meiner Anwendung etwa 5 Sekunden NACH Beendigung der Codeausführung aktualisiert wird. Allerdings, wenn ich diesen Code auf eigene Faust verwenden, ohne es in der NSOperationQUeue scheint es, das Bild fast sofort zu aktualisieren.

Die Verzögerung ist nicht ausschließlich auf einen langsamen Webserver zurückzuführen... Ich kann das Bild URL in Safari laden und es dauert weniger als eine Sekunde zu laden, oder ich kann es mit dem gleichen Code ohne die NSOperationQueue laden und es lädt viel schneller.

Gibt es eine Möglichkeit, die Verzögerung zu reduzieren, bevor mein Bild angezeigt wird, aber halten Sie eine NSOperationQueue verwenden?

6voto

Tommy Punkte 98519

Laut der Dokumentation ist der von Ihnen geschriebene Code ungültig. UIKit-Objekte dürfen nirgendwo anders als auf dem Hauptthread aufgerufen werden. Ich wette, dass das, was Sie tun, in den meisten Fällen funktioniert, aber die Anzeige nicht erfolgreich ändert, wobei der Bildschirm zufällig aus einem anderen Grund aktualisiert wird.

Apple empfiehlt nachdrücklich, dass Threads nicht der richtige Weg sind, um asynchrone URL-Abrufe auszuführen, wenn Sie batterieeffizient bleiben wollen. Stattdessen sollten Sie NSURLConnection verwenden und der Runloop erlauben, asynchrones Verhalten zu organisieren. Es ist nicht so schwer, eine schnelle Methode zu schreiben, die nur Daten zu einem NSData akkumuliert, wie es kommt dann postet die ganze Sache auf einen Delegaten, wenn die Verbindung abgeschlossen ist, aber vorausgesetzt, Sie lieber mit dem, was Sie haben, würde ich empfehlen, bleiben:

url = [NSURL URLWithString:theURLString];
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:@selector(setImageViewImage:) withObject:data waitUntilDone:YES];

...

- (void)setImageViewImage:(NSData *)data
{
    img = [[UIImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];
    NSLog(@"Image reloaded");
}

performSelectorOnMainThread tut, was der Name sagt - das Objekt wird an den angeforderten Selektor mit dem Objekt, das als einzelner Parameter angegeben wurde, auf dem Hauptthread geplant, sobald die Ausführungsschleife es erreichen kann. In diesem Fall ist "data" ein automatisch freigegebenes Objekt im Pool in dem von der NSOperation implizit erstellten Thread. Da es gültig bleiben muss, bis es verwendet wurde, habe ich waitUntilDone:YES . Eine Alternative wäre, die Daten zu etwas zu machen, das Sie explizit besitzen, und sie von der Hauptthread-Methode freigeben zu lassen.

Der Hauptnachteil dieser Methode ist, dass das Bild, wenn es in komprimierter Form zurückkommt (z.B. als JPEG oder PNG), im Hauptthread dekomprimiert wird. Um das zu vermeiden, ohne empirische Vermutungen über das Verhalten von UIImage anzustellen, die über das hinausgehen, was dokumentiert ist, um sicher zu sein, müssten Sie auf die C-Ebene zurückgehen und CoreGraphics verwenden. Ich gehe aber davon aus, dass dies den Rahmen dieser Frage sprengen würde.

1voto

Daniel Dickison Punkte 21643

Tommy ist richtig über die Notwendigkeit, alle UIKit Sachen auf dem Haupt-Thread zu tun. Allerdings, wenn Sie die Abholung auf eine Hintergrundoperation Warteschlange ausgeführt werden, gibt es keine Notwendigkeit, die NSURLConnection asynchrone Laden zu verwenden. Auch, indem Sie das Bild Dekodierung Arbeit auf die Hintergrund-Operation, halten Sie den Haupt-Thread aus blockieren während der Dekodierung des Bildes.

Sie sollten in der Lage sein, Ihren ursprünglichen Code so zu verwenden, wie er ist, aber ändern Sie einfach [imgView setImage:img] in:

[imageView performSelectorOnMainThread:@selector(setImage:)
                          withObject:img
                       waitUntilDone:NO];

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X