NSOperation être le sous-classement simultané et résiliable
-
27-09-2019 - |
Question
Je ne parviens pas à trouver une bonne documentation sur la façon de sous-classe NSOperation
être simultanée et également à l'annulation de soutien. J'ai lu les documents d'Apple, mais je ne peux pas 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 je trouve, je ne comprends pas pourquoi performSelectorOnMainThread: est utilisé. Il empêcherait mon opération de fonctionner simultanément.
En outre, quand je commente sur cette ligne, je reçois mes opérations en cours d'exécution en même temps. Cependant, le drapeau de isCancelled
n'est pas modifié, même si je l'ai appelé cancelAllOperations
.
La solution
D'accord, donc si je comprends bien, vous avez deux questions:
-
Avez-vous besoin du segment
performSelectorOnMainThread:
qui apparaît dans les commentaires dans votre code? Qu'est-ce que le code fait? -
Pourquoi le drapeau
_isCancelled
n'est pas modifié lorsque vous appelezcancelAllOperations
sur leNSOperationQueue
qui contient cette opération?
deal Let avec ces dans l'ordre. Je vais supposer que votre sous-classe de NSOperation
est appelé MyOperation
, juste pour faciliter l'explication. Je vais vous expliquer ce que vous êtes malentendu et donner un exemple corrigé.
1. En cours d'exécution en même temps NSOperations
La plupart du temps, vous utiliserez NSOperation
s avec un NSOperationQueue
et de votre code, il semble que c'est ce que vous faites. Dans ce cas, votre MyOperation
sera toujours exécuté sur un fil d'arrière-plan, quelle que soit la méthode retourne de -(BOOL)isConcurrent
, puisque NSOperationQueue
s sont explicitement conçues pour les opérations d'exécution en arrière-plan.
En tant que tel, vous n'avez pas besoin généralement de passer outre la méthode de -[NSOperation start]
, puisque par défaut, il invoque simplement la méthode -main
. C'est la méthode que vous devriez être prépondérant. La méthode -start
par défaut gère déjà la mise en isExecuting
et isFinished
pour vous au moment opportun.
Donc, si vous voulez un NSOperation
courir en arrière-plan, remplacer simplement la méthode de -main
et de le mettre sur un NSOperationQueue
.
Le performSelectorOnMainThread:
dans votre code causerait tous les cas de MyOperation
d'accomplir toujours sa tâche sur le thread principal. Comme un seul morceau de code peut être exécuté sur un fil à la fois, cela signifie qu'aucun autre MyOperation
s pourrait être en cours d'exécution. Tout le but de NSOperation
et NSOperationQueue
est de faire quelque chose en arrière-plan.
La seule fois 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 la fin de votre MyOperation
, que est quand vous devez utiliser performSelectorOnMainThread:
. Je vais vous montrer comment faire dans mon exemple ci-dessous.
2. Annulation d'une NSOperation
-[NSOperationQueue cancelAllOperations]
appelle la méthode -[NSOperation cancel]
, ce qui provoque des appels ultérieurs à -[NSOperation isCancelled]
à YES
retour. Cependant , vous avez fait deux choses pour faire de cette inefficacité.
-
Vous utilisez
@synthesize isCancelled
pour remplacer la méthode de-isCancelled
de NSOperation. Il n'y a aucune raison de le faire.NSOperation
déjà des outils-isCancelled
d'une manière tout à fait acceptable. -
Vous regardez votre propre variable d'instance de
_isCancelled
pour déterminer si l'opération a été annulée.NSOperation
garantit que[self isCancelled]
retourneraYES
si l'opération a été annulée. Finalité pas garantie que votre méthode setter personnalisée sera appelée, ni que votre propre variable d'instance est à jour. Vous devriez vérifier[self isCancelled]
Qu'est-ce que vous devez 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
. Ceux-ci sont tous traités automatiquement pour vous. Il suffit de remplacer la méthode de -main
. Il est facile.
(Une note:. Techniquement, ce n'est pas une NSOperation
« concurrente », au sens où -[MyOperation isConcurrent]
retournerait NO
mis en œuvre ci-dessus Cependant, être exécuté sur un thread d'arrière-plan Le isConcurrent
. méthode doit vraiment être nommé -willCreateOwnThread
, car c'est une description plus précise de l'intention de la méthode.)
Autres conseils
L'excellente réponse @BJHomer mérite une mise à jour.
opérations simultanées doivent remplacer la méthode de start
au lieu de main
.
Comme indiqué dans le d'Apple Documentation :
Si vous créez une opération simultanée, vous devez remplacer les méthodes suivantes et propriétés au minimum:
-
start
-
asynchronous
-
executing
-
finished
Une mise en œuvre également nécessite pour remplacer cancel
aussi bien. Faire une sous-classe thread-safe et obtenir le droit sémantique requis est également tout à fait délicate.
Ainsi, je l'ai mis une sous-classe complète et travaille comme proposition mise en œuvre à Swift dans la révision du code. Commentaires et suggestions sont les bienvenus.
Cette classe peut être facilement utilisé comme une classe de base pour votre classe opération personnalisée.
Je sais que c'est une vieille question, mais je l'ai enquêté sur ces derniers temps et ce a rencontré les mêmes exemples et avait les mêmes doutes.
Si votre travail peut être exécuté de manière synchrone dans la principale méthode, vous n'avez pas besoin d'une opération concurrente, ni début primordial, il suffit de faire votre travail et retour principal lorsque vous avez terminé.
Cependant, si votre charge de travail est asynchrone par nature - i.e. le chargement d'un NSURLConnection, vous devez commencer sous-classe. Lorsque votre retour de la méthode de démarrage, l'opération n'est pas encore terminée. Il ne sera considéré comme terminé par le NSOperationQueue lorsque vous envoyez manuellement les notifications KVO aux drapeaux isFinished et isExecuting (par exemple, une fois que les finitions de chargement d'URL ou de synchronisation tombe en panne).
Enfin, on peut vouloir expédier début du thread principal lorsque la charge de travail async vous voulez commencer à exiger une écoute en boucle d'exécution sur le thread principal. Comme le travail lui-même est asynchrone, il ne sera pas limiter votre concurrence, mais à partir du travail dans un thread de travail pourrait ne pas avoir un bon runloop prêt.
Jetez un oeil à ASIHTTPRequest . Il est un HTTP wrapper classe construite au-dessus de NSOperation
comme une sous-classe et semble mettre en œuvre. Notez que la mi-2011, le développeur recommande de ne pas utiliser l'ASI pour les nouveaux projets.
En ce qui concerne définir " annulé " propriété (ou définir " _cancelled " Ivar) à l'intérieur NSOperation sous-classe, qui 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é avec son travail. En d'autres termes, isCancelled => isFinished.
En particulier, lorsque l'objet NSOperation dépend de la réalisation d'autres objets d'exploitation, il surveille le chemin de clé isFinished pour ces objets. A défaut de générer une notification d'arrivée ( en cas d'annulation arrive ) 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 nom -willCreateOwnThread" logique BEAUCOUP !
Parce que si vous ne remplacez pas méthode commencer, il suffit d'appeler manuellement de NSOperation-objet par défaut-démarrage méthode, qui se fait appeler-fil est, par défaut, synchrone; donc, NSOperation-objet est seulement une opération non simultanée.
Cependant, si vous démarrage méthode de remplacement, à l'intérieur la mise en oeuvre méthode début, code personnalisé devrait frayer un thread séparé ... etc, alors vous cassez avec succès la restriction de « appelant fil étant par défaut synchrone », ce qui rend donc NSOperation-objet devient une opération concurrente, il peut fonctionner de manière asynchrone par la suite.
Ce billet de blog:
http://www.dribin.org/dave / blog / Archives / 2009/09/13 / snowy_concurrent_operations /
explique pourquoi vous pourriez avoir besoin:
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
dans votre méthode start
.