1

J'ai un async NSOperation pour télécharger des données pour plusieurs objets ImageFile. Comme tout cela se passe de manière asynchrone, j'utilise un groupe de répartition pour suivre les demandes, puis dispatch_group_notify pour terminer l'opération quand elles sont toutes terminées.Que se passe-t-il avec dispatch_group_enter et dispatch_group_leave?

La question que j'ai est ce qui se passe lorsque l'opération est terminée prématurément, soit par annulation ou par une autre erreur. Le groupe de répartition sera laissé avec dispatch_group_enter et dispatch_group_leave de sorte que dispatch_group_notify ne sera jamais appelé. Est-ce le bloc retenu quelque part par le système qui attend toujours, ou va-t-il être libéré lorsque le NSOperation sera libéré?

Ou est-ce que mon approche n'est pas idéale, comment pourrais-je faire autrement?

- (void)main 
{ 
    if (self.cancelled) { 
     [self completeOperation]; 
     return; 
    } 

    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    context.persistentStoreCoordinator = self.persistentStoreCoordinator; 
    context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; 

    [context performBlock:^{ 

     NSFetchRequest *request = [ImageFile fetchRequest]; 
     request.predicate = .... 
     request.sortDescriptors = ... 

     NSError *error; 
     NSArray *imageFiles = [context executeFetchRequest:request error:&error]; 
     if (!imageFiles) { 
      // Error handling... 
      [self completeOperation]; 
      return; 
     } 

     dispatch_group_t group = dispatch_group_create(); 

     for (ImageFile *imageFile in imageFiles) { 
      dispatch_group_enter(group); 
      @autoreleasepool { 
       [self.webService requestImageWithId:imageFile.id completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 

        if (self.cancelled) { 
         [self completeOperation]; 
         return; 
        } 

        [context performBlock:^{ 
         if (data && !error) { 
          imageFile.data = data; 
          NSError *error; 
          if (![context save:&error]) { 
           // Error handling... 
           [self completeOperation]; 
           return; 
          } 
         } 
         dispatch_group_leave(group); 
        }]; 
       }]; 
      } 
     } 

     dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
      [self completeOperation]; 
     }); 
    }]; 
} 

Répondre

2

De l'docs for dispatch_group_enter():

Un appel à cette fonction doit être équilibrée avec un appel à dispatch_group_leave.

De l'docs for dispatch_group_t:

Le groupe d'expédition conserve la trace de combien de blocs sont en circulation, et GCD conserve le groupe jusqu'à ce que tous ses blocs associés exécution complète.

Il est question de blocs en attente, mais ce que cela signifie réellement, c'est des appels sans correspondance à dispatch_group_enter(). Donc, la réponse à votre question à propos de ce qui se passe est que l'objet groupe de répartition fuit effectivement. L'objet de bloc passé à dispatch_group_notify() et tous les objets qu'il a de fortes références à également fuir. Dans votre cas, cela inclut self.

La réponse à votre question de savoir si votre approche est «idéale» est: non, ce n'est pas idéal. Ce n'est même pas valide par le contrat de conception de GCD. Vous devez équilibrer tous les appels à dispatch_group_enter() avec des appels à dispatch_group_leave(). Si vous souhaitez distinguer en quelque sorte entre succès et échec ou achèvement et annulation normaux, vous devez définir un état disponible pour le bloc notify, puis coder le bloc notify pour consulter cet état afin de décider quoi faire.

Dans votre cas, cependant, les chemins de code où vous ne parvenez pas à appeler dispatch_group_leave() font exactement la même chose que le bloc notify. Donc, je ne suis même pas sûr pourquoi vous n'appelez pas seulement dispatch_group_leave() plutôt que d'appeler [self completeOperation] dans ces cas.