50 votes

Subclassing NSOperation pour être simultané et annulable

Je n'arrive pas à trouver une bonne documentation sur la façon de sous-classer NSOperation pour être concomitante et aussi pour supporter l'annulation. J'ai lu la documentation d'Apple, mais je ne trouve pas d'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 l'exécution simultanée de mon opération.

De plus, lorsque je commente cette ligne, mes opérations s'exécutent en même temps. Cependant, les isCancelled n'est pas modifié, même si j'ai appelé cancelAllOperations .

110voto

BJ Homer Points 29168

Ok, donc si je comprends bien, vous avez deux questions :

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

  2. Pourquoi le _isCancelled n'est 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 juste pour faciliter l'explication. Je vais expliquer ce que vous comprenez mal, puis donner un exemple corrigé.

1. Exécution simultanée de NSOpérations

La plupart du temps, vous utiliserez NSOperation avec un NSOperationQueue et d'après votre code, il semble que c'est ce que vous faites. Dans ce cas, votre MyOperation sera toujours exécuté sur un thread d'arrière-plan, indépendamment de ce que l'on attend de l'utilisateur. -(BOOL)isConcurrent retourne, puisque la méthode NSOperationQueue sont explicitement conçus pour exécuter des opérations en arrière-plan.

Ainsi, il n'est généralement pas nécessaire de remplacer l'option -[NSOperation start] puisque, par défaut, elle invoque simplement la méthode -main méthode. C'est cette méthode que vous devez surcharger. La méthode par défaut -start gère déjà la mise en place de la méthode isExecuting y isFinished pour vous aux moments opportuns.

Donc si vous voulez un NSOperation pour qu'il fonctionne en arrière-plan, il suffit de remplacer l'option -main et le placer sur un NSOperationQueue .

En performSelectorOnMainThread: dans votre code entraînerait chaque instance de MyOperation pour qu'il effectue toujours sa tâche sur le thread principal. Puisqu'un seul morceau de code peut être exécuté sur un thread à la fois, cela signifie qu'aucun autre thread ne peut être exécuté. MyOperation pourraient fonctionner. Le but de NSOperation y NSOperationQueue est de faire quelque chose en arrière-plan.

Le seul moment où vous voulez forcer les choses sur le thread principal est lorsque vous mettez à jour l'interface utilisateur. Si vous avez besoin de mettre à jour l'interface utilisateur lorsque votre programme MyOperation finitions, que c'est quand vous devez utiliser performSelectorOnMainThread: . Je vais vous montrer comment faire dans mon exemple ci-dessous.

2. Annulation d'une opération d'ONS

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

  1. Vous utilisez @synthesize isCancelled pour remplacer l'option de la NSOperation -isCancelled méthode. Il n'y a aucune raison de le faire. NSOperation met déjà en œuvre -isCancelled d'une manière parfaitement acceptable.

  2. Vous vérifiez votre propre _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. Il fait pas ne garantit pas que votre méthode setter personnalisée sera appelée, ni que votre propre variable d'instance est à jour. Vous devez 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 pas besoin de faire quoi que ce soit avec isExecuting , isCancelled ou isFinished . Tout cela est géré automatiquement pour vous. Il suffit de remplacer le -main méthode. C'est aussi simple que cela.

(Note : techniquement, ce n'est pas un "concurrent". NSOperation dans le sens où -[MyOperation isConcurrent] rendrait NO tel que mis en œuvre ci-dessus. Cependant, il se être exécuté sur un fil d'arrière-plan. Le site isConcurrent devrait vraiment s'appeler -willCreateOwnThread car c'est une description plus précise de l'intention de la méthode).

5voto

2voto

nobre Points 2019

Je sais que c'est une vieille question, mais j'ai enquêté sur ce sujet dernièrement et j'ai rencontré les mêmes exemples et j'ai eu les mêmes doutes.

Si tout votre travail peut être exécuté de manière synchrone à l'intérieur de la méthode main, vous n'avez pas besoin d'une opération concurrente, ni d'une surcharge de start, faites simplement votre travail et revenez de main quand vous avez terminé.

Toutefois, si votre charge de travail est asynchrone par nature, c'est-à-dire si vous chargez une NSURLConnection, vous devez sous-classer start. Lorsque votre méthode start revient, l'opération n'est pas encore terminée. Elle ne sera considérée comme terminée par la NSOperationQueue que lorsque vous enverrez manuellement des notifications KVO aux drapeaux isFinished et isExecuting (par exemple, lorsque le chargement asynchrone de l'URL se termine ou échoue).

Enfin, on peut vouloir dispatcher le démarrage vers le thread principal lorsque la charge de travail asynchrone que l'on veut démarrer nécessite une boucle d'exécution écoutant le thread principal. Comme le travail lui-même est asynchrone, cela ne limitera pas la concurrence, mais le démarrage du travail dans un thread de travailleur peut ne pas avoir une boucle d'exécution appropriée prête.

0voto

makdad Points 4670

Jetez un coup d'œil à ASIHTTPRequest . Il s'agit d'une classe enveloppante HTTP construite au-dessus de NSOperation en tant que sous-classe et semble les mettre en œuvre. Notez que depuis la mi-2011, le développeur recommande de ne pas utiliser ASI pour les nouveaux projets.

0voto

J-Q Points 75

En ce qui concerne la définition de " annulé "(ou définir la propriété " Annulé " iVAR) dans la sous-classe NSOperation, normalement ce n'est PAS nécessaire. Simplement parce que lorsque l'utilisateur déclenche l'annulation, le code personnalisé doit toujours notifier aux observateurs de la KVO que votre opération est maintenant terminé avec son travail. En d'autres termes, isCancelled => isFinished.

En particulier, lorsque l'objet NSOperation dépend de l'achèvement d'autres objets opération, il surveille le chemin de clé isFinished de ces objets. Si une notification d'achèvement n'est pas généré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 nommé -willCreateOwnThread" a BEAUCOUP de sens !

En effet, si vous ne surchargez pas la méthode de démarrage et que vous appelez simplement manuellement la méthode de démarrage par défaut de NSOperation-Object, le call-thread lui-même est, par défaut, synchrone ; ainsi, NSOperation-Object n'est qu'une opération non simultanée.

Cependant, si vous passez outre start-method, dans l'implémentation de start-method, le custom-code devrait donner lieu à un fil de discussion distinct etc, alors vous avez réussi à briser la restriction de "l'appel du thread par défaut étant synchrone", faisant ainsi de NSOperation-Object une opération concurrente, elle peut être exécutée de manière asynchrone par la suite.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X