web-dev-qa-db-fra.com

Comment fonctionne un gestionnaire de complétion sur iOS?

Im essayant de comprendre les gestionnaires et les blocs d'achèvement. Je crois que vous pouvez utiliser des blocs pour de nombreuses choses de programmation profonde sans gestionnaires d'achèvement, mais je pense que je comprends que les gestionnaires d'achèvement sont basés sur des blocs. (Donc, fondamentalement, les gestionnaires d'achèvement ont besoin de blocs, mais pas l'inverse).

J'ai donc vu ce code sur Internet à propos de l'ancien framework 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;
    }];

Ici, nous appelons une méthode qui fait des trucs (effectue TWRequest) et retourne une fois terminé avec responseData & urlResponse & error. Ce n'est qu'à son retour qu'il exécute le bloc qui teste l'accord et arrête l'indicateur d'activité. PARFAIT!

Maintenant, c'est une configuration que j'ai pour une autre application qui fonctionne, mais j'essaie de rassembler les morceaux:

@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);
            });
        }
    });
}

Voici ma compréhension:

  1. fetchUsersWithCompletionHandler est évidemment l'homologue de performRequestWithHandler
  2. malheureusement c'est un peu plus complexe car il y a un appel GCD sur le chemin ...

Mais fondamentalement, la demande est exécutée et l'erreur est traitée, les données sont traitées et ensuite, le gestionnaire est vérifié. Ma question est, comment fonctionne cette partie gestionnaire? Je comprends que si elle existe, elle sera renvoyée dans la file d'attente principale et retournera le tableau usersArray. Mais comment sait-il d'attendre que le tableau usersArray soit rempli? Je suppose que ce qui me déroute, c'est que la méthode: block dans ce cas a un autre bloc à l'intérieur, l'appel dispatch_async. Je suppose que ce que je cherche, c'est la logique qui fait vraiment des trucs et sait QUAND renvoyer la réponseData et urlResponse. Je sais que ce n'est pas la même application, mais je ne peux pas voir le code de performRequestWithHandler.

18
marciokoko

Fondamentalement, dans ce cas, cela fonctionne comme ça:

  1. Vous appelez fetchUsersWithCompletionHandler: à partir de n'importe quel thread que vous aimez (probablement le principal).
  2. Il initialise NSURLRequest, puis appelle: dispatch_async (dispatch_get_global_queue ... qui crée fondamentalement un bloc et le planifie pour le traitement sur une file d'attente en arrière-plan.
  3. Puisqu'il s'agit de dispath_async, le thread actuel quitte la méthode fetchUsersWithCompletionHandler :.

    ...
    le temps passe, jusqu'à ce que la file d'attente en arrière-plan dispose de ressources gratuites
    ...

  4. Et maintenant, lorsque la file d'attente en arrière-plan est libre, elle consomme une opération planifiée (Remarque: elle exécute la requête synchrone - elle attend donc les données):

    NSURLResponse *response;
    NSError *error = nil;
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                 returningResponse:&response
                                                             error:&error];
    ...
    
  5. Une fois que les données arrivent, alors sersArray est rempli.

  6. Le code continue dans cette partie:

    if (handler){
        dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
        });
    }
    
  7. Maintenant, si nous avons spécifié un gestionnaire, il planifie le blocage de l'appel sur une file d'attente principale. Il s'agit de dispatch_sync, donc l'exécution sur le thread actuel ne se poursuivra que lorsque le thread principal aura terminé avec le bloc. À ce stade, le thread d'arrière-plan attend patiemment.

    ...
    un autre moment passe
    ...

  8. Maintenant, la file d'attente principale a des ressources libres, donc elle consomme au-dessus du bloc et exécute ce code (en passant le tableau des utilisateurs précédemment rempli au `` gestionnaire ''):

    handler(usersArray);
    
  9. Une fois cela fait, il revient du bloc et continue de consommer tout ce qu'il se trouve dans la file d'attente principale.

  10. Puisque le thread principal est fait avec le bloc, le thread d'arrière-plan (bloqué à dispatch_sync) peut également continuer. Dans ce cas, il revient simplement du bloc.

Edit: Quant aux questions que vous avez posées:

  1. Ce n'est pas comme si la file d'attente principale/d'arrière-plan était toujours occupée, c'est peut-être juste. (en supposant que la file d'attente en arrière-plan ne prend pas en charge les opérations simultanées comme la principale). Imaginez le code suivant, qui est exécuté sur un thread principal:

        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");
    

Puisque les deux sont dispatch_async appels, vous planifiez leur exécution les uns après les autres. Mais la tâche n ° 2 ne sera pas traitée immédiatement par la file d'attente principale, car elle doit d'abord quitter la boucle d'exécution en cours, deuxièmement, elle doit d'abord terminer la tâche n ° 1.

Donc, la sortie du journal sera comme ça:

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

Vous avez:

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

Qui déclare le type de bloc défini comme Handler qui a void type de retour et qui accepte NSArray * comme paramètre.

Plus tard, vous avez votre fonction:

+(void)fetchUsersWithCompletionHandler:(Handler)handler

Qui prend comme bloc de paramètres de type Handler et permet d'y accéder en utilisant le nom local handler.

Et étape # 8:

handler(usersArray);

Qui appelle juste directement le bloc handler (comme vous appeliez n'importe quelle fonction C/C++) et lui passe usersArray comme paramètre.

29
deekay