2013-12-20 7 views
18

Eine NSURLSession können Sie eine große Anzahl von NSURLSessionTask hinzufügen, um im Hintergrund herunterladen.Durchschnittlicher Fortschritt aller NSURLSessionTasks in einer NSURLSession

Wenn Sie den Fortschritt eines einzelnen NSURLSessionTask überprüfen wollen, dann ist es so einfach wie

double taskProgress = (double)task.countOfBytesReceived/(double)task.countOfBytesExpectedToReceive;

Aber was ist der beste Weg den durchschnittlichen Fortschritt aller NSURLSessionTasks in einem NSURLSession zu überprüfen?

Ich dachte, ich würde versuchen, den Fortschritt aller Aufgaben im Durchschnitt:

[[self backgroundSession] getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *allDownloadTasks) { 

    double totalProgress = 0.0; 

    for (NSURLSessionDownloadTask *task in allDownloadTasks) { 

     double taskProgress = (double)task.countOfBytesReceived/(double)task.countOfBytesExpectedToReceive; 

     if (task.countOfBytesExpectedToReceive > 0) { 
      totalProgress = totalProgress + taskProgress; 
     } 
     NSLog(@"task %d: %.0f/%.0f - %.2f%%", task.taskIdentifier, (double)task.countOfBytesReceived, (double)task.countOfBytesExpectedToReceive, taskProgress*100); 
    } 

    double averageProgress = totalProgress/(double)allDownloadTasks.count; 

    NSLog(@"total progress: %.2f, average progress: %f", totalProgress, averageProgress); 
    NSLog(@" "); 

}]; 

Aber die Logik ist hier falsch: Angenommen, Sie 50 Aufgaben erwarten 100MB zum Download erwarten 1MB und 3 Aufgaben zum Download bereit. Wenn die 50 kleinen Aufgaben vor den 3 großen Aufgaben abgeschlossen werden, wird averageProgress viel höher sein als der tatsächliche durchschnittliche Fortschritt.

So haben Sie durchschnittlich Fortschritte berechnen nach der TOTALcountOfBytesReceived dividiert durch die TOTALcountOfBytesExpectedToReceive. Aber das Problem ist, dass ein NSURLSessionTask diese Werte nur herausfindet, sobald es startet, und es möglicherweise nicht beginnt, bis eine andere Aufgabe beendet wird.

Wie überprüfen Sie den durchschnittlichen Fortschritt aller NSURLSessionTasks in einem NSURLSession?

+1

Sie könnten für jede Datei eine Header-Anfrage senden und die "Content-Length" zu Ihrem Gesamtfortschritt hinzufügen. – HAS

+0

Das wird funktionieren. Aber in meinem Fall stoße ich auf Fälle, in denen ich 200 Downloads gleichzeitig starten muss, so dass alle entsprechenden Header-Anfragen lange dauern werden. Es lohnt sich jedoch, diesen Ansatz näher auszuführen, es lohnt sich also, wenn Sie ihn als Antwort veröffentlichen. – Eric

+0

Da Sie mit mehr als 200 Elementen zu tun haben, warum sollte der Fortschritt nicht die Anzahl der abgeschlossenen Dateien über die Gesamtzahl sein? Wir tun dies für das Herunterladen von mehr als 300 Bildern. Es ist definitiv zu viel Aufwand, um den genauen, byteweisen Prozentsatz zu berechnen. Meine zwei Cents :) –

Antwort

13

Bevor Sie beginnen, die Dateien herunterzuladen, können Sie winzige kleine HEAD requests senden, um die Dateigrößen zu erhalten. Sie fügen einfach ihre expectedContentLength hinzu und haben Ihre endgültige Downloadgröße.

- (void)sizeTaskForURL:(NSURL *)url 
{ 

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; 
    [request setHTTPMethod:@"HEAD"]; 

    NSURLSessionDataTask *sizeTask = 
    [[[self class] dataSession] 
    dataTaskWithRequest:request 
    completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) 
    { 
     if (error == nil) 
     { 
      NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 

      if ([httpResponse statusCode] == 200) { 

       totalBytesExpectedToReceive += (double)[httpResponse expectedContentLength]; 

       numberOfFileSizesReceived++; 

       NSLog(@"%lu/%lu files found. file size: %f", (unsigned long)numberOfFileSizesReceived, (unsigned long)numberOfTasks, totalBytesExpectedToReceive); 

       if (numberOfFileSizesReceived == numberOfTasks){ 
        NSLog(@"%lu/%lu files found. total file size: %f", (unsigned long)numberOfFileSizesReceived, (unsigned long)numberOfTasks, totalBytesExpectedToReceive); 
       } 
      } 
      else { 
       NSLog(@"Bad status code (%ld) for size task at URL: %@", (long)[httpResponse statusCode], [[response URL] absoluteString]); 
      } 
     } 
     else 
     { 
      NSLog(@"Size task finished with error: %@", error.localizedDescription); 
     } 
    }]; 

    [sizeTask resume]; 

} 

die Dateien herunterladen danach

Dies ist, wie es aussieht:

Auf der linken Seite können Sie sehen, dass, wenn es die HEAD requests sendet sie noch nicht die UIProgressView läuft. Wenn es fertig ist, lädt es die Dateien herunter.

App

Also, wenn Sie große Dateien herunterladen könnte es nützlich sein, um „Abfall“ jene Sekunden HEAD-Anfragen machen und zeigen nun an dem Benutzer den richtigen Fortschritt anstatt einig falschen Fortschritte.

Während der Downloads möchten Sie (definitiv) die Delegate-Methoden verwenden, um kleinere Unterteilungen neuer Daten zu erhalten (andernfalls wird die Fortschrittsanzeige "springen").

15

Ah ja. Ich erinnere mich, dass ich mich 1994 damit beschäftigt habe, OmniWeb zu schreiben. Wir haben eine Reihe von Lösungen ausprobiert, einschließlich der Tatsache, dass der Fortschrittsbalken statt des Fortschritts blinkt (nicht populär) oder dass er wächst, wenn neue Aufgaben herausfinden, wie groß sie in die Warteschlange aufgenommen werden (verärgert, weil sie gesehen haben) umgekehrter Fortschritt manchmal).

Am Ende, was die meisten Programme entschieden haben (einschließlich Nachrichten in iOS 5 und Safari) ist eine Art von Cheat: zum Beispiel in Nachrichten sie wussten, dass die durchschnittliche Zeit zum Senden einer Nachricht etwa 1,5 Sekunden war (Beispiel nur Zahlen), so animierten sie den Fortschrittsbalken, um ungefähr 1,5 Sekunden zu beenden, und würden nur um 1,4 Sekunden verzögern, wenn die Nachricht noch nicht tatsächlich gesendet worden war.

Moderne Browser (wie Safari) variieren diesen Ansatz, indem sie die Aufgabe in Abschnitte unterteilen und für jeden Abschnitt einen Fortschrittsbalken anzeigen. Wie (nur Beispiel) könnte Safari annehmen, dass das Nachschlagen einer URL in DNS normalerweise 0,2 Sekunden dauert, also werden sie die erste 1/10 (oder was auch immer) der Fortschrittsanzeige über 0,2 Sekunden animieren, aber natürlich werden sie es tun überspringen Sie (oder warten Sie bei der 1/10-Marke), wenn die DNS-Suche kürzer oder länger dauert.

In Ihrem Fall weiß ich nicht, wie vorhersehbar Ihre Aufgabe ist, aber es sollte einen ähnlichen Cheat geben. Wie, gibt es eine durchschnittliche Größe für die meisten Dateien? Wenn ja, sollten Sie in der Lage sein, herauszufinden, wie lange 50 dauert. Oder Sie teilen Ihren Fortschrittsbalken einfach in 50 Segmente auf und füllen jedes Mal, wenn eine Datei abgeschlossen ist, ein Segment und animieren basierend auf der aktuellen Anzahl an Bytes/Sekunde, die Sie erhalten oder basierend auf der Anzahl der Dateien/Sekunde, die Sie erhalten haben weit oder jede andere Metrik, die Sie mögen.Ein Trick besteht darin, das Paradox von Zeno zu verwenden, wenn Sie den Fortschrittsbalken starten oder stoppen müssen - nicht einfach anhalten oder zur nächsten Marke springen, sondern einfach langsamer (und langsamer werden) oder schneller werden (und behalten) Beschleunigung) bis du dahin gekommen bist, wo die Bar sein muss.

Viel Glück!

+1

Das funktioniert * gut * wenn Sie die durchschnittliche Größe von ** jeder ** Datei, die Sie herunterladen, vorher genau wissen. Aber wenn Sie dieses Wissen nicht vor der Zeit haben (was bei vielen Menschen der Fall ist, einschließlich bei mir), ist dieser Ansatz nur ein ausgeklügeltes Rätselraten. Und das ist der Zeno-Trick. – Eric

+0

Ich werde deine Antwort trotzdem auffrischen, weil ich deine Erklärung klar und die Geschichte interessant fand :-) – Eric

+0

Es funktioniert oft, denn wenn die Dateien riesig sind, ist die meiste Zeit nur die Overhead Verbindung/Start Fetch/etc. Also jede Datei von 1-1000 Bytes ist im Wesentlichen die gleiche Zeit zu holen. Und wie gesagt, das ist der Stand der Technik. –

2

Ich habe zwei mögliche Lösungen für Sie. Sie bekommen nicht genau das, wonach Sie suchen, aber beide geben dem Benutzer ein Verständnis davon, was vor sich geht.

Erste Lösung Sie haben mehrere Fortschrittsbalken. Ein großer Balken, der den Fortschritt der Dateinummer angibt (50 von 200 abgeschlossen). Dann haben Sie mehrere Fortschrittsbalken darunter (Anzahl von ihnen gleich der Menge der gleichzeitigen Downloads möglich, in meinem Fall ist dies 4, in Ihrem kann es unhandlich sein). So kennt der Benutzer sowohl feingranulare Details zu den Downloads als auch einen Gesamtfortschritt (der sich nicht mit Download-Bytes, sondern mit Download-Abschluss bewegt).

Zweite Lösung ist eine Multi-Datei Fortschrittsbalken. Dieser kann täuschen, da die Dateien unterschiedliche Größen haben, aber Sie könnten einen Fortschrittsbalken erstellen, der in eine Anzahl von Chunks aufgeteilt wird, die der Anzahl der heruntergeladenen Dateien entspricht. Dann geht jeder Teil des Balkens von 0% auf 100% basierend auf dem Herunterladen einer einzelnen Datei. So könnten Sie mittlere Abschnitte des Fortschrittsbalkens gefüllt haben, während Anfang und Ende leer sind (Dateien nicht heruntergeladen).

Auch dies ist keine Lösung für die Frage, wie man Bytes aus mehreren Dateien herunterladen kann, aber sie sind eine alternative Benutzeroberfläche, so dass der Benutzer alles versteht, was gerade passiert. (Ich mag Option 1 am besten)

3

Ihr ist ein spezieller Fall des Problems "Fortschrittsbalken". Konferieren, z.B. dies reddit thread.

Es ist schon immer, und als Wil sagen schien, kann es bedevilingly schwer, auch für Megacorp, Inc. ™ zu bekommen, „einfach so“. Z.B. Selbst mit vollständig genauen MB-Werten können Sie ohne Netzwerkprobleme einfach "hängen". Ihre App funktioniert noch, aber die Wahrnehmung kann sein, dass Ihr Programm hängt.

Und die Suche nach extrem "genauen" Werten kann die fortschreitende Aufgabe überkompensieren. Vorsichtig auftreten.

Verwandte Themen