2013-08-17 18 views
18

Sto cercando di capire i gestori di completamento & blocchi. Credo che tu possa usare i blocchi per molte cose di programmazione profonda senza gestori di completamento, ma penso di capire che i gestori di completamento sono basati su blocchi. (Quindi fondamentalmente gli addetti al completamento hanno bisogno di blocchi ma non viceversa).Come funziona un gestore di completamento su iOS?

così ho visto questo codice su internet circa il vecchio quadro twitter:

[twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) { 
     if (!error) { 
      self.successLabel.text = @"Tweeted Successfully"; 
      [self playTweetSound]; 
     } else { 
      // Show alert 
     } 
     // Stop indicator 
     sharedApplication.networkActivityIndicatorVisible = NO; 
    }]; 

Qui stiamo chiamando un metodo che fa cose (esegue TWRequest) e ritorna al termine con responseData & urlResponse errore &. Solo quando ritorna esegue il blocco che i test hanno concesso e ferma l'indicatore di attività. PERFEZIONARE!

Ora, questa è una messa a punto che ho per un'altra app che funziona, ma sto cercando di mettere insieme i pezzi:

@interface 
Define an ivar 
typedef void (^Handler)(NSArray *users); 
Declare the method 
+(void)fetchUsersWithCompletionHandler:(Handler)handler; 

@implementation 
+(void)fetchUsersWithCompletionHandler:(Handler)handler { 
    //...Code to create NSURLRequest omitted... 
    __block NSArray *usersArray = [[NSArray alloc] init]; 

    //A. Executes the request 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ 

     // Peform the request 
     NSURLResponse *response; 
     NSError *error = nil; 
     NSData *receivedData = [NSURLConnection sendSynchronousRequest:request 
                returningResponse:&response 
                   error:&error]; 
     // Deal with your error 
     if (error) { 
      } 
      NSLog(@"Error %@", error); 
      return; 
     } 
     // Else deal with data 
     NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding]; 
     usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil]; 

     // Checks for handler & returns usersArray to main thread - but where does handler come from & how does it know to wait tip usersArray is populated? 
     if (handler){ 
      dispatch_sync(dispatch_get_main_queue(), ^{ 
      handler(usersArray); 
      }); 
     } 
    }); 
} 

Qui è la mia comprensione:

  1. fetchUsersWithCompletionHandler è, ovviamente, l'omologo di performRequestWithHandler
  2. sfortunatamente questo è un po 'più complesso perché c'è una chiamata GCD nel modo ...

In pratica, la richiesta viene eseguita e l'errore viene gestito, i dati vengono gestiti e quindi il gestore viene controllato. La mia domanda è, come funziona questa parte del gestore? Capisco se esiste, quindi tornerà alla coda principale e restituirà l'array Users. Ma come fa ad aspettare che venga compilato usersArray? Immagino che ciò che mi confonde sia il fatto che il metodo: block in questo caso ha un altro blocco al suo interno, la chiamata dispatch_async. Immagino che quello che sto cercando sia la logica che in realtà fa roba e sa QUANDO restituire la risposta Data e urlResponse. So che non è la stessa app, ma non riesco a vedere il codice per performRequestWithHandler.

risposta

29

In sostanza in questo caso funziona così:

  1. Si chiama fetchUsersWithCompletionHandler: da qualunque discussione vi piace (probabilmente principale forma uno).
  2. Esso inizializza NSURLRequest, poi chiama:. dispatch_async (dispatch_get_global_queue ... che crea fondamentalmente blocco, e gli orari per l'elaborazione su una coda sfondo
  3. Dal momento che è dispath_async, thread corrente lascia la fetchUsersWithCompletionHandler: metodo.

    ...
    tempo passa, fino a coda di sfondo ha alcune risorse gratuite
    ...

  4. Ed ora, quando il backgr ound coda è libera, consuma funzionamento programmato (Nota: Esegue sincrono richiesta - in modo che attende i dati):

    NSURLResponse *response; 
    NSError *error = nil; 
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request 
                  returningResponse:&response 
                     error:&error]; 
    ... 
    
  5. volta che i dati viene, poi il usersArray è popolato.

  6. Codice continua a questa parte:

    if (handler){ 
        dispatch_sync(dispatch_get_main_queue(), ^{ 
         handler(usersArray); 
        }); 
    } 
    
  7. Ora, se abbiamo gestore specificato, orari bloccare per l'invocazione in una coda principale. È dispatch_sync, quindi l'esecuzione sul thread corrente non procederà fino a quando il thread principale non verrà eseguito con il blocco. A questo punto, il thread in background attende pazientemente.

    ...
    un altro momento passa
    ...

  8. coda Ora principale ha alcune risorse gratuite, così si consuma al di sopra del blocco, ed esegue questo codice (passando usersArray precedentemente popolato al 'gestore'):

    handler(usersArray); 
    
  9. Una volta fatto, torna dal blocco e continua consumando tutto ciò che è nella coda principale.

  10. Poiché il thread principale viene eseguito con il blocco, anche il thread in background (bloccato su dispatch_sync) può procedere ulteriormente. In questo caso, ritorna semplicemente dal blocco.

Edit: Per quanto riguarda le domande che ha chiesto:

  1. Non è come coda/sfondo principale sarà sempre occupato, è solo, può essere. (supponendo che la coda di sfondo non supporti operazioni concorrenti come quella principale). Immaginare seguente codice, che è in esecuzione su un thread principale:

    dispatch_async(dispatch_get_main_queue(), ^{ 
         //here task #1 that takes 10 seconds to run 
         NSLog(@"Task #1 finished"); 
        }); 
        NSLog(@"Task #1 scheduled"); 
    
        dispatch_async(dispatch_get_main_queue(), ^{ 
         //here task #2 that takes 5s to run 
         NSLog(@"Task #2 finished"); 
        }); 
        NSLog(@"Task #2 scheduled"); 
    

Poiché entrambi sono dispatch_async chiamate, pianificare questi per l'esecuzione uno dopo l'altro. Ma l'attività n. 2 non verrà elaborata immediatamente dalla coda principale, poiché prima deve lasciare il ciclo di esecuzione corrente, in secondo luogo deve completare prima l'attività n.

Così l'output del registro sarà così:

Task #1 scheduled 
Task #2 scheduled 
Task #1 finished 
Task #2 finished 

2.You hanno:

typedef void (^Handler)(NSArray *users); 

che dichiara blocco typedefe'd come Handler che ha void tipo di ritorno e che accetta come NSArray * parametro.

Più tardi, avete la vostra funzione:

+(void)fetchUsersWithCompletionHandler:(Handler)handler 

che prende come un blocco di parametri di tipo Handler e consentire l'accesso ad esso utilizzando nome locale handler.

E passo # 8:

handler(usersArray); 

Il che chiama direttamente handler blocco (come se stessi chiamando/funzione C++ qualsiasi C) e passa usersArray come parametro ad esso.

+0

2 domande: (1) perché nel passaggio 3 si dice "il tempo passa, finché la coda di sfondo ha alcune risorse gratuite". Perché NON dovrebbe avere risorse gratuite? lo stesso nei passaggi 7/8 con la coda principale. Perché NON dovrebbe avere risorse libere e quindi aspettare? E il più importante, in che modo il gestore "gestisce" l'array Users per inviarlo? Quale parte del codice dice al gestore di inviare i dati a chi lo ha chiamato? – marciokoko

+0

Vedere la mia risposta modificata. – deekay

+0

Qual è la necessità di convertire 'NSData' in' NSString' e poi di nuovo in 'NSData'? – Rishab