2014-10-13 6 views
6

Ich habe eine for-Schleife mit drei asynchronen Methoden, und ich möchte eine Behandlung zu machen, nachdem diese 3 asynchronen Methoden abgeschlossen sind.Warte auf eine asynchrone Methode in einer for-Schleife zu beenden

-(void)getAllUsersInformations{ 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
      for(User *user in users){ 
       [self getUserInfo:user]; 
      } 
     //Here, I want to reload the table view for example, after finishing the for loop (executing the whole three methods). 
    }); 
} 

-(void)getUserInfo:(User*)user{ 
    [self getInformations:user]; 
    [self getExperiences:user]; 
    [self getEducation:user]; 
} 

Haben Sie irgendeine Technik, um dieses Ergebnis zu haben? Vielen Dank.

+0

Sind 'getInformations',' getExperiences' und 'getEducation', selbst, asynchrone Methoden? Oder sind das alles synchrone Methoden, die Sie asynchron an eine Hintergrundwarteschlange senden? (Ihre Bearbeitung der Frage ändert die Antwort radikal.) – Rob

+0

@Rob; Ja, sie sind asynchron! 'getAllUsersInformations'' getInformations' 'getExperiences' und' getEducation' sind asynchron. – androniennn

+0

@androniennn kannst du die drei '-getXXX'-Methoden synchron machen? –

Antwort

14

Ein GCD-Ansatz besteht darin, dispatch_group zu verwenden. Bevor Sie eine asynchrone Task starten, rufen Sie daher dispatch_group_enter auf. Wenn die asynchrone Task beendet ist, rufen Sie dispatch_group_leave auf, und Sie können dann eine dispatch_group_notify erstellen, die beim Beenden der asynchronen Tasks aufgerufen wird. Sie können mit einem Abschluss Blockmuster heiraten dies (das ist eine gute Idee für asynchrone Methoden ist, sowieso):

  1. Wenn getInformations, getExperiences und getEducation sind, sich alle asynchronen Methoden, das erste, was Sie brauchen, ist etwas Mechanismus zu wissen, wann sie fertig sind. Eine übliche Lösung besteht darin, für jeden ein Komplettierungsblockmuster zu implementieren. Zum Beispiel:

    // added completionHandler parameter which will be called when the retrieval 
    // of the "informations" is done. 
    
    - (void)getInformations:(User*)user completionHandler:(void (^)(void))completionHandler { 
        // do whatever you were before, but in the asynchronous task's completion block, call this 
        // completionHandler() 
        // 
        // for example 
    
        NSURLRequest *request; 
    
        [NSURLConnection sendAsynchronousRequest:request queue:nil completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 
         // handle the request here 
    
         // the important thing is that the completion handler should 
         // be called _inside_ the this block 
    
         if (completionHandler) { 
          completionHandler(); 
         } 
        }]; 
    } 
    

    Wiederholen Sie diesen Vorgang für getExperiences und getEducation auch.

  2. Dann können Sie eine Versandgruppe verwenden Sie benachrichtigt werden, wenn jede dieser drei Anträge getan werden getan, einen Abschluss Block in getUserInfo Aufruf, wenn diese stattfindet:

    // added completion handler that will be called only when `getInformations`, 
    // `getExperiences` and `getEducation` are all done. 
    // 
    // this takes advantage of the completion block we added to those three 
    // methods above 
    
    - (void)getUserInfo:(User*)user completionHandler:(void (^)(void))completionHandler { 
        dispatch_group_t group = dispatch_group_create(); 
    
        // start the three requests 
    
        dispatch_group_enter(group); 
        [self getInformations:user completionHandler:^{ 
         dispatch_group_leave(group); 
        }]; 
    
        dispatch_group_enter(group); 
        [self getExperiences:user completionHandler:^{ 
         dispatch_group_leave(group); 
        }]; 
    
        dispatch_group_enter(group); 
        [self getEducation:user completionHandler:^{ 
         dispatch_group_leave(group); 
        }]; 
    
        // this block will be called asynchronously only when the above three are done 
    
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
         if (completionHandler) { 
          completionHandler(); 
         } 
        }); 
    } 
    
  3. Und Sie dann wiederholen dieser Prozess am getAllUsersInformations:

    // call new getUserInfo, using dispatch group to keep track of whether 
    // all the requests are done 
    
    -(void)getAllUsersInformations { 
    
        dispatch_group_t group = dispatch_group_create(); 
    
        for(User *user in users){ 
         dispatch_group_enter(group); 
    
         [self getUserInfo:user completionHandler:^{ 
          dispatch_group_leave(group); 
         }]; 
        } 
    
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
         [self.tableView reloadData]; 
        }); 
    } 
    

Zwei abschließende Gedanken:

  1. all das skizzierte haben, muss ich gestehen, dass ich wahrscheinlich diese Anforderungen in Concurrent/Asynchron benutzerdefinierte NSOperation Subklassen anstelle der Verwendung von Versandgruppen wickeln würde. Siehe den Abschnitt "Konfigurieren von Operationen für die gleichzeitige Ausführung" von Concurrency Programming Guide. Dies ist ein radikaleres Refactoring des Codes, daher werde ich das hier nicht angehen, aber Sie können die Anzahl dieser Anfragen begrenzen, die gleichzeitig ausgeführt werden, um potenzielle Timeout-Probleme zu vermeiden.

  2. Ich weiß nicht, wie viele dieser Benutzeranfragen ausgeführt werden, aber Sie sollten möglicherweise die Benutzeroberfläche aktualisieren, wenn Benutzerinformationen eintreffen, anstatt darauf zu warten, dass alles fertig ist. Dies ist wiederum eine radikalere Umgestaltung des Codes, könnte aber zu etwas führen, das sich besser anspricht.

+0

Was für eine Erklärung! Verstanden die Technik, und ich werde versuchen, es umzusetzen. Ich werde eine Rückmeldung hinterlassen;). – androniennn

+0

Das letzte 'group_notify', das die Tabellenansicht erneut lädt, wird nie aufgerufen. Ich weiß nicht warum, besonders weil die 'group_leave' von' getUserInfo' aufgerufen wird. – androniennn

+0

Stellen Sie zuerst sicher, dass die letzte Gruppenbenachrichtigung nicht aufgerufen wurde (und dass es nicht nur ein Problem ist, dass das Neuladen der Tabelle nicht ordnungsgemäß funktioniert hat). Zweitens, um dies zu diagnostizieren, loggen Sie die Gruppeneingaben und verlassen Sie sie in 'getAllUsersInformations' und stellen Sie sicher, dass sie _alle_ aufgerufen haben. – Rob

-1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    // Background work 
     for(User *user in users){ 
      [self getUserInfo:user]; 
     } 

    dispatch_async(dispatch_get_main_queue(), ^{ 
    //reload tableview , this is on main thread. 
    }); 
}); 
+0

Die drei Methoden in '-getUserInfo:' sind asynchron, führt dies nicht dazu, dass dies zu einem frühen Zeitpunkt zurückkehrt? –

+0

@AaronA. : Genau Aaron. – androniennn

+0

Nein, diese Methoden werden synchron im Hintergrund aufgerufen. In der Tat, wenn Sie einen Haltepunkt beim Betreten des Blocks setzen, wird es darüber hinweggehen. Wenn die for-Schleife beendet wird, wird der Block 'dispatch_async (dispatch_get_main_queue(),^{ // reload tableview, dies ist im Haupt-Thread. });' wird am Haupt-Thread aufgerufen. Machen Sie einen schnellen Test, es sollte funktionieren. –

0

Versuchen Sie, einen Block mit Abschluss zu machen, Sie können dies nicht mit einer for-Schleife tun, wenn die Methoden async sind. Sie müssen getUserInfo einzeln nach dem Abschluss des vorherigen Aufrufs aufrufen. Ich denke, das wird dein Problem lösen.

-(void)getAllUsersInformations{ 
    [self registerUserAtIndex:0]; 
    } 

    - (void) registerUserAtIndex: (NSInteger) userIndex 
    { 
    RegisterOperation *op = [[RegisterOperation alloc] initWithUser:[users objectAtIndex:userIndex]]; 
    [RegisterOperation setResultCompletionBlock:^(BOOL *finished, NSInteger userIndex) { 
     dispatch_async(dispatch_get_main_queue(), ^{ 
     if (userIndex++ < [users count] { 
     [self registerUserAtIndex:userIndex++]; 
     } else { 
     [myTableView reloadData]; 
     } 
    }]; 
    [[NSOperationQueue mainQueue] addOperation:op]; 
} 

Hoffe, das wird Ihnen helfen.

0

Rop Antwort mit swift:

func processData() 
{ 
    let group: dispatch_group_t = dispatch_group_create() 

    for item in data as! Object { 
     dispatch_group_enter(group) 
     item.process(completion: {() -> (Void) in 
      dispatch_group_leave(group) 
     }) 
     } 

    dispatch_group_notify(group, dispatch_get_main_queue(), { 
     //Do whatever you want 
     }) 
} 
Verwandte Themen