2010-11-30 3 views
5

Ich versuche eine iPad-App zu schreiben, die ein Bild von einer URL lädt. Ich bin mit dem folgenden Bild Ladecode:Signifikante Verzögerung beim Laden von Bildern mit UIImage von URL asynchron

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

All dieses Codes zu einem NSOperationQueue als eine Operation hinzugefügt wird, so dass es asynchron geladen wird und nicht dazu führen, meine Anwendung, wenn das Bild des websever langsam einzusperren. Ich habe die NSLog-Zeile hinzugefügt, so dass ich in der Konsole sehen konnte, wenn dieser Code fertig war.

Ich habe ständig festgestellt, dass das Bild in meiner App etwa 5 Sekunden nach der Ausführung des Codes aktualisiert wird. Wenn ich diesen Code jedoch selbst verwende, ohne ihn in NSOperationQUeue zu setzen, scheint es das Bild fast sofort zu aktualisieren.

Die Verzögerung wird nicht vollständig durch einen langsamen Webserver verursacht ... Ich kann die Bild URL in Safari laden und es dauert weniger als eine Sekunde, um zu laden, oder ich kann es mit dem gleichen Code ohne die NSOperationQueue und es laden lädt viel schneller.

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

Antwort

6

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

Apple empfiehlt dringend, dass Threads nicht die Möglichkeit sind, asynchrone URL-Aufrufe durchzuführen, wenn Sie batteriesparend bleiben möchten. Stattdessen sollten Sie NSURLConnection verwenden und dem Runloop ermöglichen, asynchrones Verhalten zu organisieren. Es ist nicht schwer, eine schnelle Methode zu schreiben, die Daten nur an eine NSData anhäuft, wenn sie kommt, und dann das Ganze an einen Delegierten weiterleitet, wenn die Verbindung hergestellt ist, aber vorausgesetzt, dass Sie lieber bei dem bleiben, was ich empfehle :

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 schon sagt - das Objekt gesendet wird, um die Wähler mit der als einzelner Parameter auf dem Hauptthread gegebenen Objekt angefordert werden planen, sobald die Laufschleife kann um es zu bekommen. In diesem Fall ist 'data' ein automatisch freigegebenes Objekt im Pool im Thread, das implizit von NSOperation erstellt wurde. Weil Sie es brauchen, um gültig zu bleiben, bis es benutzt worden ist, habe ich waitUntilDone:YES benutzt. Eine Alternative wäre, Daten zu erstellen, die Sie explizit besitzen und die die Hauptthread-Methode freigeben.

Der Hauptnachteil dieser Methode besteht darin, dass das komprimierte Bild (z. B. JPEG oder PNG) im Hauptthread dekomprimiert wird. Um dies zu vermeiden, ohne empirische Vermutungen über das Verhalten von UIImage zu machen, die über das hinausgehen, was als sicher dokumentiert ist, müssten Sie auf die C-Ebene wechseln und CoreGraphics verwenden. Aber ich gehe davon aus, dass dies über den Rahmen dieser Frage hinausgeht.

+0

Danke, Tommy! Ich werde es mir heute Nacht ansehen und sehen, wo ich von dem, was du mir gesagt hast, komme.Eigentlich habe ich heute Morgen verschiedene Möglichkeiten gelesen, Bilder in Coacoa herunterzuladen, und ich habe selbst entdeckt, dass, als ich den gesamten Bildbearbeitungscode mit NSUrlRequest und NSURLConnection neu schrieb, es das Bild wie erwartet zu laden schien. Ich bin mir immer noch nicht sicher, ob ich die neue Methode, die ich geschrieben habe, verwenden werde oder den Code, den Sie mir gezeigt haben, aber Optionen zu haben, ist großartig, und es in beide Richtungen zu schreiben war eine gute Lernerfahrung. Nochmals vielen Dank für Ihre Hilfe und danke, dass Sie sich mit einem Neuling wie mir abgeben! :) – Jackson

+0

Übrigens, wenn jemand diese Frage liest, fand ich diesen Artikel sehr hilfreich bei der Implementierung eines NSUrLRequest basierten Bildladeprogramms. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html – Jackson

1

Tommy hat recht damit, dass man alle UIKit-Sachen im Hauptthread machen muss. Wenn Sie jedoch den Abruf in einer Hintergrundoperationswarteschlange ausführen, müssen Sie das asynchrone NSURLConnection-Laden nicht verwenden. Wenn Sie die Bilddecodierung für die Hintergrundoperation beibehalten, verhindern Sie außerdem, dass der Haupt-Thread blockiert, während das Bild dekodiert wird.

sollten Sie in der Lage sein, wie ist Ihr Original-Code zu verwenden, aber ändern Sie einfach [imgView setImage: img] zu:

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

Wow, das behebt das Problem. Vielen Dank! Das war so einfach! Ich bin mir immer noch nicht ganz sicher warum. Ich werde darüber nachlesen müssen. Nun, da ich eine vollständig implementierte und funktionierende NSUrConnection-Methode (die für sich selbst asynchron ist) und eine NSData/dataWithContentsOfURL-Methode mit NSObjectQueue für asynchronicity-Methode, die funktioniert, welche empfehlen Sie, die ich verwende? Was sind die Stärken von NSUrlConnection vs. NSData und umgekehrt? Ich habe gehört, dass NSUrConnection mehr Flexibilität beim Caching hat und Fehler besser handhabt. Ist das wahr? Danke nochmal! – Jackson

+0

Es ist wahr, dass NSURLConnection Ihnen mehr Flexibilität gibt. Die Verwendung des asynchronen Ladens von NSURLConnection in einer NSOperation im Hintergrund ist jedoch ziemlich schwierig, da Sie eine eigene Ausführungsschleife einrichten müssen. Sie können '- [NSURLConnection sendSynchronousRequest: returningResponse: error:]' -Methode verwenden, um etwas von der von NSURLConnection gebotenen Flexibilität zu erhalten (siehe NSMutableURLRequest), während Sie die Dinge synchron halten. Das ist wahrscheinlich, was ich tun würde, wenn Sie nicht komplexe HTTP-Redirect-/Authentifizierungs-/Caching-Anforderungen haben. –

+0

Was "warum" diese Lösung funktioniert ist, dass der Ansichtszeichnungscode, der auf dem Hauptthread ausgeführt wird, wissen muss, dass Sie nur das Bild der Bildansicht geändert haben. Durch den Aufruf von setImage: In einem Hintergrund-Thread wird vermutlich der Haupt-Thread nie benachrichtigt, dass neuer Inhalt gezeichnet werden muss (bis etwas anderes die Ansicht aktualisiert). Es ist möglich, dass Sie viel schlechteres Verhalten erhalten, indem Sie dies im Hintergrundthread tun - z. wenn Sie das Bild ändern, während der Hauptthread gerade versucht, es zu zeichnen. –

Verwandte Themen