web-dev-qa-db-fra.com

Le sous-classement de NSOperation doit être simultané et annulable

Je suis incapable de trouver une bonne documentation sur la manière de sous-classer NSOperation pour être simultané et pour prendre en charge l'annulation. J'ai lu la documentation Apple, mais je suis incapable de trouver un exemple "officiel".

Voici mon code source:

@synthesize isExecuting = _isExecuting;
@synthesize isFinished = _isFinished;
@synthesize isCancelled = _isCancelled;

- (BOOL)isConcurrent
{
    return YES;
}

- (void)start
{
/* WHY SHOULD I PUT THIS ?
    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }
*/

    [self willChangeValueForKey:@"isExecuting"];
    _isExecuting = YES;
    [self didChangeValueForKey:@"isExecuting"];


    if (_isCancelled == YES)
    {
        NSLog(@"** OPERATION CANCELED **");
    }
    else
    {
        NSLog(@"Operation started.");
        sleep(1);
        [self finish];
    }
}

- (void)finish
{
    NSLog(@"operationfinished.");

    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];

    _isExecuting = NO;
    _isFinished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];

    if (_isCancelled == YES)
    {
        NSLog(@"** OPERATION CANCELED **");
    }
}

Dans l'exemple que j'ai trouvé, je ne comprends pas pourquoi performSelectorOnMainThread: est utilisé. Cela empêcherait mon opération de fonctionner simultanément.

De plus, lorsque je commente cette ligne, mes opérations sont exécutées simultanément. Cependant, l'indicateur isCancelled n'est pas modifié, même si j'ai appelé cancelAllOperations.

49
thierryb

Bon, si j'ai bien compris, vous avez deux questions:

  1. Avez-vous besoin du segment performSelectorOnMainThread: qui apparaît dans les commentaires de votre code? Que fait ce code?

  2. Pourquoi le drapeau _isCancelled n'est-il pas modifié lorsque vous appelez cancelAllOperations sur le NSOperationQueue contenant cette opération?

Traitons-les dans l'ordre. Je vais supposer que votre sous-classe de NSOperation s'appelle MyOperation, juste pour faciliter l'explication. Je vais expliquer ce que vous avez mal compris, puis donner un exemple corrigé.

1. Exécution simultanée de NSOperations

La plupart du temps, vous utiliserez NSOperations avec un NSOperationQueue, et d'après votre code, cela ressemble à ce que vous faites. Dans ce cas, votre MyOperation sera toujours exécuté sur un thread en arrière-plan, quel que soit le résultat de la méthode -(BOOL)isConcurrent, puisque NSOperationQueues sont explicitement conçus pour exécuter des opérations en arrière-plan.

En tant que tel, vous n'avez généralement pas besoin de remplacer la méthode -[NSOperation start] car, par défaut, elle appelle simplement la méthode -main. C’est la méthode que vous devriez utiliser. La méthode par défaut -start gère déjà les réglages de isExecuting et isFinished aux moments appropriés.

Donc, si vous voulez qu'un NSOperation s'exécute en arrière-plan, substituez simplement la méthode -main et placez-la sur un NSOperationQueue.

Le performSelectorOnMainThread: dans votre code obligerait chaque instance de MyOperation à toujours exécuter sa tâche sur le thread principal. Comme un seul élément de code peut être exécuté sur un thread à la fois, cela signifie qu'aucun autre MyOperations ne peut être en cours d'exécution. Le but de NSOperation et NSOperationQueue est de faire quelque chose en arrière-plan.

Le seul moment où vous souhaitez forcer des éléments sur le thread principal est lorsque vous mettez à jour l'interface utilisateur. Si vous devez mettre à jour l'interface utilisateur à la fin de votre MyOperation, alors que vous devez utiliser performSelectorOnMainThread:. Je montrerai comment faire cela dans mon exemple ci-dessous.

2. Annuler une opération NSO

-[NSOperationQueue cancelAllOperations] appelle la méthode -[NSOperation cancel], qui entraîne les appels ultérieurs à -[NSOperation isCancelled] pour renvoyer YES. Cependant, vous avez fait deux choses pour rendre cela inefficace.

  1. Vous utilisez @synthesize isCancelled pour remplacer la méthode -isCancelled de NSOperation. Il n'y a aucune raison de faire cela. NSOperation implémente déjà -isCancelled de manière parfaitement acceptable.

  2. Vous vérifiez votre propre variable d'instance _isCancelled pour déterminer si l'opération a été annulée. NSOperation garantit que [self isCancelled] retournera YES si l'opération a été annulée. Cela ne pas garantit que votre méthode de définition personnalisée sera appelée, ni que votre propre variable d'instance est à jour. Vous devriez vérifier [self isCancelled]

Ce que vous devriez faire

L'en-tête:

// MyOperation.h
@interface MyOperation : NSOperation {
}
@end

Et la mise en place:

// MyOperation.m
@implementation MyOperation

- (void)main {
    if ([self isCancelled]) {
        NSLog(@"** operation cancelled **");
    }

    // Do some work here
    NSLog(@"Working... working....")

    if ([self isCancelled]) {
        NSLog(@"** operation cancelled **");
    }
    // Do any clean-up work here...

    // If you need to update some UI when the operation is complete, do this:
    [self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO];

    NSLog(@"Operation finished");
}

- (void)updateButton {
    // Update the button here
}
@end

Notez que vous n'avez rien à faire avec isExecuting, isCancelled ou isFinished. Ceux-ci sont tous traités automatiquement pour vous. Remplacez simplement la méthode -main. C'est si facile.

(Remarque: techniquement, il ne s'agit pas d'un "concurrent" NSOperation, en ce sens que -[MyOperation isConcurrent] renverrait NO tel qu'implémenté ci-dessus. Cependant, sera exécuté sur un thread d’arrière-plan. La méthode isConcurrent devrait vraiment être nommée -willCreateOwnThread, car il s’agit d’une description plus précise de l’intention de la méthode.)

109
BJ Homer

L'excellente réponse de @BJHomer mérite une mise à jour. 

Les opérations simultanées doivent remplacer la méthode start au lieu de main

Comme indiqué dans la Documentation Apple :

Si vous créez une opération simultanée, vous devez au moins redéfinir les méthodes et propriétés suivantes:

  • start
  • asynchronous
  • executing
  • finished

Une implémentation appropriée nécessite également que substitue également cancel. Créer une sous-classe thread-safe et obtenir la bonne sémantique requise est également assez délicat. 

Ainsi, j'ai mis une sous-classe complète et fonctionnelle sous la forme d'une proposition implémentée dans Swift in Code Review. Commentaires et suggestions sont les bienvenus.

Cette classe peut facilement être utilisée comme classe de base pour votre classe d'opération personnalisée.

3
CouchDeveloper

Je sais que c’est une vieille question, mais j’ai enquêté sur cela récemment et j’ai rencontré les mêmes exemples et les mêmes doutes.

Si tout votre travail peut être exécuté de manière synchrone dans la méthode principale, vous n'avez pas besoin d'opération simultanée, ni de remplacement du début, vous effectuez simplement votre travail et revenez de la tâche principale à la fin.

Toutefois, si votre charge de travail est de nature asynchrone - c’est-à-dire lors du chargement d’une connexion NSURLConnection, vous devez démarrer la sous-classe. Lorsque votre méthode de démarrage revient, l'opération n'est pas encore terminée. NSOperationQueue ne le considère comme terminé que lorsque vous envoyez manuellement des notifications KVO aux indicateurs isFinished et isExecuting (par exemple, une fois le chargement de l'URL asynchrone échoué).

Enfin, il peut être utile de répartir le démarrage sur le thread principal lorsque le workload async que vous souhaitez démarrer nécessite une exécution en boucle sur le thread principal. Comme le travail lui-même est asynchrone, cela ne limitera pas votre concurrence, mais le démarrage du travail dans un thread de travail risque de ne pas disposer d'un cycle de lancement approprié.

2
nobre

En ce qui concerne la propriété define "cancelled" (ou définissez "iVAR _cancelled") dans la sous-classe NSOperation, ce n’est normalement pas nécessaire. Tout simplement parce que lorsque USER déclenche l'annulation, le code personnalisé doit toujours informer les observateurs KVO que votre opération est maintenant terminée avec son travail. En d'autres termes, isCancelled => isFinished.

En particulier, lorsque l'objet NSOperation dépend de l'achèvement d'autres objets d'opération, il surveille le chemin d'accès à la clé isFinished pour ces objets. Ne pas générer de notification d'arrivée (en cas d'annulation) peut donc empêcher l'exécution d'autres opérations dans votre application.


BTW, la réponse de @BJ Homer: "La méthode isConcurrent devrait vraiment être named -willCreateOwnThread" a beaucoup de sens

Parce que si vous NE substituez PAS la méthode de démarrage, appelez simplement manuellement la méthode de démarrage par défaut de NSOperation-Object, le processus d'appel lui-même est, par défaut, synchrone; NSOperation-Object n’est donc qu’une opération non concurrente.

Cependant, si vous substituez la méthode start à la méthode start, le code personnalisé devrait générer un thread distinct… etc., puis vous supprimez avec succès la restriction de "la commande par défaut du thread appelant étant synchrone", rendant ainsi NSOperation -Objet devenant une opération simultanée, il peut ensuite être exécuté de manière asynchrone.

0
J-Q

Jetez un oeil à ASIHTTPRequest . Il s'agit d'une classe de wrapper HTTP construite au-dessus de NSOperation en tant que sous-classe et qui semble les implémenter. Notez qu'à partir de la mi-2011, le développeur recommande de ne pas utiliser ASI pour les nouveaux projets.

0
makdad