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

Je ne suis pas en mesure de trouver une bonne documentation sur la façon de sous- NSOperation pour être simultané et pour prendre en charge l’annulation. Je lis les documents Apple, mais je ne suis pas en mesure 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 fonctionnent simultanément. Cependant, le drapeau isCancelled n’est pas modifié, même si j’ai appelé cancelAllOperations .

    Bon, si je comprends bien, 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 l’indicateur _isCancelled n’est-il pas modifié lorsque vous appelez cancelAllOperations sur le NSOperationQueue qui contient cette opération?

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

    1. Exécution simultanée de NSOperations

    La plupart du temps, vous utiliserez NSOperation avec un NSOperationQueue , et à partir de votre code, cela ressemble à ce que vous faites. Dans ce cas, votre MyOperation sera toujours exécuté sur un thread d’arrière-plan, indépendamment du retour de la méthode -(BOOL)isConcurrent , car les NSOperationQueue 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 elle invoque par défaut simplement la méthode -main . C’est la méthode que vous devriez ignorer. La méthode par défaut isExecuting gère déjà le paramètre isExecuting et isFinished pour vous aux moments appropriés.

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

    Le performSelectorOnMainThread: dans votre code provoquerait que chaque instance de MyOperation toujours sa tâche sur le thread principal. Comme un seul morceau de code peut être exécuté sur un thread à la fois, cela signifie qu’aucun autre MyOperation ne peut être exécuté. NSOperation et NSOperationQueue pour but de faire quelque chose en arrière-plan.

    Le seul moment où vous voulez forcer des choses sur le thread principal est lorsque vous mettez à jour l’interface utilisateur. Si vous devez mettre à jour l’interface utilisateur lorsque votre MyOperation termine, vous devez utiliser performSelectorOnMainThread: Je vais montrer comment faire cela dans mon exemple ci-dessous.

    2. Annuler une NSOperation

    -[NSOperationQueue cancelAllOperations] appelle la méthode -[NSOperation cancel] , ce qui provoque les appels suivants à -[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 -isCancelled . Il n’y a aucune raison de le faire. NSOperation implémente déjà -isCancelled d’une 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 garantit pas que votre méthode de réglage 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 œuvre:

     // 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. Il suffit de remplacer la méthode -main . C’est si facile.

    (Une note: techniquement, ce n’est pas une NSOperation “concurrente”, dans le sens où -[MyOperation isConcurrent] renverrait NO comme implémenté ci-dessus. Cependant, il sera exécuté sur un thread en arrière-plan. La méthode isConcurrent devrait être nommée -willCreateOwnThread , car il s’agit d’une description plus précise de l’intention de la méthode.)

    Je sais que c’est une vieille question, mais j’ai enquêté sur ces derniers temps et rencontré les mêmes exemples et eu 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 démarrage prioritaire, faites simplement votre travail et revenez de main lorsque vous avez terminé.

    Cependant, si votre charge de travail est asynchrone par nature – c’est-à-dire que vous chargez une NSURLConnection, vous devez démarrer la sous-classe. Lorsque votre méthode de démarrage retourne, l’opération n’est pas encore terminée. Il ne sera considéré comme terminé par NSOperationQueue que lorsque vous envoyez manuellement des notifications KVO aux indicateurs isFinished et isExecuting (par exemple, une fois que le chargement de l’URL asynchrone est terminé ou échoue).

    Enfin, vous pouvez envoyer le début au thread principal lorsque le workload async que vous souhaitez démarrer nécessite une boucle d’exécution sur le thread principal. Comme le travail lui-même est asynchrone, il ne limitera pas votre access simultané, mais le démarrage du travail dans un thread de travail peut ne pas avoir un bon programme d’exécution.

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

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

    Comme indiqué dans la documentation Apple :

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

    • start
    • asynchronous
    • executing
    • finished

    Une implémentation correcte nécessite également de remplacer cancel . Rendre une sous classe thread-safe et obtenir la sémantique requirejse est également assez compliqué.

    Ainsi, j’ai mis une sous-classe complète et fonctionnelle en tant que proposition implémentée dans Swift in Code Review. Les 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.

    Jetez un oeil à ASIHTTPRequest . C’est une classe d’encapsuleur HTTP construite sur NSOperation tant que sous-classe et semble les implémenter. Notez qu’à la mi-2011, le développeur recommande de ne pas utiliser ASI pour les nouveaux projets.

    En ce qui concerne la définition de la propriété ” cancelée ” (ou la définition de iVAR_cancelled “) dans la sous-classe NSOperation, cela n’est normalement PAS nécessaire. Simplement parce que lorsque USER déclenche l’annulation, le code personnalisé doit toujours informer les observateurs de KVO que votre opération est maintenant terminée . En d’autres termes, isCancelled => isFinished.

    En particulier, lorsque l’object NSOperation dépend de l’achèvement d’autres objects d’opération, il surveille le chemin de la clé isFinished pour ces objects. Si vous ne parvenez pas à générer une notification finale ( en cas d’annulation ), vous risquez d’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 s’appeler -willCreateOwnThread” fait BEAUCOUP le sens !

    Parce que si vous ne remplacez PAS la méthode de démarrage, appelez simplement manuellement la méthode de démarrage par défaut de NSOperation-Object, par défaut, le thread appelant est synchrone; NSOperation-Object n’est donc qu’une opération non concurrente.

    Cependant, si vous substituez la méthode de démarrage à l’implémentation de la méthode de démarrage, le code personnalisé devrait engendrer un thread séparé … etc, puis vous briser la ressortingction de «synchronisation par défaut du thread appelant», rendant NSOperation-Object opération simultanée, il peut être exécuté de manière asynchrone par la suite.

    Cet article de blog:

    http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/

    explique pourquoi vous pourriez avoir besoin de:

     if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; return; } 

    dans votre méthode de start .