2013-12-09 9 views
8

Ich weiß, es gibt another similar question, aber es ist für eine ältere Version von AFNetworking, und beantwortet es nicht wirklich sowieso.AFNetworking-2 waitUntilFinished funktioniert nicht

Ich habe den folgenden Code:

AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager]; 
manager.securityPolicy.allowInvalidCertificates = YES; 
manager.requestSerializer = [AFJSONRequestSerializer serializer]; 
[manager.requestSerializer setAuthorizationHeaderFieldWithUsername: currentUser() password: currentPassword()]; 
__block NSDictionary* response = nil; 
AFHTTPRequestOperation* operation = [manager 
    GET: @"https://10.20.30.40:8765/foobar" 
    parameters: [NSDictionary dictionary] 
    success:^(AFHTTPRequestOperation* operation, id responseObject){ 
     response = responseObject; 
     NSLog(@"response (block): %@", response); 
    } 
    failure:^(AFHTTPRequestOperation* operation, NSError* error){ 
     NSLog(@"Error: %@", error);} 
]; 
[operation waitUntilFinished]; 
NSLog(@"response: %@", response); 
... 

Wenn ich das ausführen, was ich in meinem Log sehen werden ist:

2013-12-09 09:26:20.105 myValve[409:60b] response: (null) 
2013-12-09 09:26:20.202 myValve[409:60b] response (block): { 
    F00005 = ""; 
    F00008 = ""; 
    F00013 = ""; 
} 

Die NSLog die erste nach die waitUntilFinished Feuer ist . Ich habe erwartet, dass es an zweiter Stelle feuert. Was vermisse ich?

Antwort

32

Ein paar Gedanken:

  1. Das Problem ist, dass waitUntilFinished für das Kernnetzbetrieb wartet abgeschlossen ist, aber es wird nicht für die success oder failure Abschluss Blöcke warten. Wenn Sie für die Fertigstellung Blöcke warten wollen, können Sie eine Semaphore verwenden:

    __block NSDictionary* response = nil; 
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 
    
    manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    
    AFHTTPRequestOperation* operation = [manager GET: @"https://10.20.30.40:8765/foobar" 
                 parameters: [NSDictionary dictionary] 
                 success:^(AFHTTPRequestOperation* operation, id responseObject){ 
                  response = responseObject; 
                  NSLog(@"response (block): %@", response); 
                  dispatch_semaphore_signal(semaphore); 
                 } 
                 failure:^(AFHTTPRequestOperation* operation, NSError* error){ 
                  NSLog(@"Error: %@", error); 
                  dispatch_semaphore_signal(semaphore); 
                 }]; 
    
    NSLog(@"waiting"); 
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 
    // [operation waitUntilFinished]; 
    NSLog(@"response: %@", response); 
    

    können Sie alternativ wickeln diese in Ihren eigenen vier gleichzeitige NSOperation Unterklasse, isFinished in den AFHTTPRequestOperation Abschluss Blöcke veröffentlichen, die semaphore Beseitigung in der verarbeiten.

    Hinweis: Stellen Sie sicher, dass completionQueue angegeben wird, wenn Semaphore in der Hauptwarteschlange ausgeführt werden, da AFNetworking standardmäßig Abwesenheitsvervollständigungshandler an die Hauptwarteschlange sendet, und Sie können Deadlocks ausführen.

  2. Nebenbei sollten Sie nie die Hauptwarteschlange blockieren (schlechte UX, Ihre App könnte durch Watchdog-Prozess getötet werden, etc.), also wenn Sie dies aus der Hauptwarteschlange tun, würde ich die entmutigen Verwendung von entweder waitUntilFinished oder dem Semaphor. Es ist besser, nur zu initiieren, was Sie von innerhalb der Fertigstellung Blöcke benötigen, die Hauptwarteschlange lassen weiterhin die Ausführung während dieses asynchrone Netzbetrieb läuft, zB:

    [activityIndicatorView startAnimating]; 
    
    AFHTTPRequestOperation* operation = [manager GET: @"https://10.20.30.40:8765/foobar" 
                 parameters: [NSDictionary dictionary] 
                 success:^(AFHTTPRequestOperation* operation, id responseObject){ 
    
                  // do whatever you want with `responseObject` here 
    
                  // now update the UI, e.g.: 
    
                  [activityIndicatorView stopAnimating]; 
                  [self.tableView reloadData]; 
                 } 
                 failure:^(AFHTTPRequestOperation* operation, NSError* error){ 
    
                  // put your error handling here 
    
                  // now update the UI, e.g.: 
    
                  [activityIndicatorView stopAnimating]; 
                 }]; 
    
    // NSLog(@"waiting"); 
    // dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 
    // // [operation waitUntilFinished]; 
    // NSLog(@"response: %@", response); 
    

Es klingt wie Sie wollen Lassen Sie Ihr Modell die UI alle notwendigen Aktualisierungen vornehmen, wenn das Modellobjekt seine Aktualisierungen vorgenommen hat. Sie können also Ihre eigenen Blockparameter verwenden, damit der View-Controller dem Modellobjekt mitteilen kann, was zu tun ist, wenn es fertig ist (anstatt waitUntilFinished oder Semaphor zu verwenden, damit der Netzwerkbetrieb die Hauptwarteschlange blockiert). Zum Beispiel, nehmen wir an, Ihr Modell wie dieses eine Methode hatte:

- (void)updateModelWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failure 
{ 
    AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager]; 
    manager.securityPolicy.allowInvalidCertificates = YES; 
    manager.requestSerializer = [AFJSONRequestSerializer serializer]; 
    [manager.requestSerializer setAuthorizationHeaderFieldWithUsername: currentUser() password: currentPassword()]; 
    AFHTTPRequestOperation* operation = [manager GET: @"https://10.20.30.40:8765/foobar" 
              parameters: [NSDictionary dictionary] 
              success:^(AFHTTPRequestOperation* operation, id responseObject){ 

               // do your model update here 

               // then call the success block passed to this method (if any), 
               // for example to update the UI 

               if (success) 
                success(); 
              } 
              failure:^(AFHTTPRequestOperation* operation, NSError* error){ 
               NSLog(@"Error: %@", error); 

               // if caller provided a failure block, call that 

               if (failure) 
                failure(error); 
              }]; 
} 

Dann wird Ihr View-Controller kann so etwas tun:

[modelObject updateModelWithSuccess:^{ 
    // specify UI updates to perform upon success, e.g. 
    // stop activity indicator view, reload table, etc. 
} failure:^(NSError *error){ 
    // specify any UI updates to perform upon failure 
}] 

Unterm Strich Ihr Code die gleiche Art von Abschluss-Blöcke verwenden können, AFNetworking verwendet. Wenn Sie möchten, dass das Modell Informationen zurückgibt, können Sie den Completion-Blöcken selbst weitere Parameter hinzufügen, aber ich nehme an, dass das obige die Grundidee darstellt.

+4

Leider Option # 1 hängt nur die Dinge vollständig. Kein Block feuert jemals. Nicht sicher, warum es das tut, aber es tut es. –

+0

Ich verstehe die Sache "hängen die UI ist schlecht".Was ich in diesem Fall mit dem Problem habe, sind REST-Abfragen, um lokale Modelle zu aktualisieren. Also würde ich gerne dieses AF-Zeug in die Modellklassen legen, um das Update zu machen. Also muss ich dann eine dieser Methoden mit undWhenYourDone: Blocks erweitern, damit ich bei Bedarf UI-Updates auslösen kann. –

+0

@TravisGriggs Dann geben Sie einfach die 'updateModel'-Methode Block Parameter Ihres Modells (nicht anders als die' Erfolg' und 'Fehler' Blöcke, die AFNetworking selbst verwendet). Auf diese Weise kann der View-Controller effektiv sagen: "Aktualisieren Sie das Modell, und wenn Sie fertig sind, machen Sie x, y und z". Siehe überarbeitete Antwort. – Rob

Verwandte Themen