Sottoclasse NSOperation essere simultaneo e cancellabile
-
27-09-2019 - |
Domanda
Non sono in grado di trovare una buona documentazione su come sottoclasse NSOperation
di essere concorrente e anche per la cancellazione supporto. Ho letto la documentazione di Apple, ma non riesco a trovare un esempio "ufficiale".
Ecco il mio codice sorgente:
@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 **");
}
}
Nell'esempio ho trovato, non capisco il motivo per cui performSelectorOnMainThread: viene utilizzato. Si impedirebbe la mia operazione di esecuzione contemporaneamente.
Inoltre, quando io commento che la linea, ho le mie operazioni in esecuzione contemporaneamente. Tuttavia, la bandiera isCancelled
non viene modificato, anche se ho chiamato cancelAllOperations
.
Soluzione
Ok, se ho capito bene, si hanno due domande:
-
Avete bisogno il segmento
performSelectorOnMainThread:
che appare nei commenti nel codice? Che cosa significa che il codice fare? -
Perché la bandiera
_isCancelled
non viene modificata quando si chiamacancelAllOperations
sulNSOperationQueue
che contiene questa operazione?
L'accordo di Let con questi in ordine. Ho intenzione di assumere che la vostra sottoclasse di NSOperation
si chiama MyOperation
, solo per facilità di spiegazione. Mi spiego quello che stai incomprensione e poi dare un esempio corretto.
1. NSOperations in esecuzione contemporaneamente
La maggior parte del tempo, che verrà utilizzato NSOperation
s con NSOperationQueue
, e dal codice, suona come questo è quello che stai facendo. In tal caso, il MyOperation
sarà sempre eseguito su un thread in background, indipendentemente da ciò il metodo restituisce -(BOOL)isConcurrent
, dal momento che NSOperationQueue
s sono esplicitamente progettati per le operazioni eseguite in background.
In quanto tale, in genere non è necessario eseguire l'override del metodo -[NSOperation start]
, dal momento che per impostazione predefinita invoca semplicemente il metodo -main
. Questo è il metodo che dovrebbe essere prevalente. Il metodo predefinito -start
gestisce già l'impostazione isExecuting
e isFinished
per voi al momento opportuno.
Quindi, se si desidera un NSOperation
per l'esecuzione in background, è sufficiente eseguire l'override del metodo -main
e metterlo su un NSOperationQueue
.
Il performSelectorOnMainThread:
nel codice causerebbe ogni istanza di MyOperation
di svolgere sempre il suo compito sul thread principale. Poiché solo un pezzo di codice può essere in esecuzione su un thread alla volta, questo significa che nessun altro MyOperation
s potrebbero essere in esecuzione. Lo scopo di NSOperation
e NSOperationQueue
è quello di fare qualcosa in background.
L'unica volta che si vuole forzare le cose sul thread principale è quando si sta aggiornando l'interfaccia utente. Se è necessario aggiornare l'interfaccia utente quando i vostri finiture MyOperation
, che è quando si dovrebbe usare performSelectorOnMainThread:
. Vi mostrerò come fare nel mio esempio qui sotto.
2. Annullamento di un NSOperation
-[NSOperationQueue cancelAllOperations]
chiama il metodo -[NSOperation cancel]
, che provoca chiamate successive -[NSOperation isCancelled]
a YES
ritorno. Tuttavia , avete fatto due cose per rendere questo inefficace.
-
Si utilizza
@synthesize isCancelled
di sovrascrivere il metodo-isCancelled
di NSOperation. Non c'è alcuna ragione per farlo.NSOperation
già implementa-isCancelled
in modo perfettamente accettabile. -
Si sta verificando la propria variabile di istanza
_isCancelled
per determinare se l'operazione è stata annullata. garanzieNSOperation
che[self isCancelled]
restituiràYES
se l'operazione è stata annullata. Lo fa non la garanzia che il vostro metodo setter personalizzato sarà chiamato, né che la variabile propria istanza sia aggiornato. Si dovrebbe essere il controllo[self isCancelled]
Cosa si dovrebbe fare
L'intestazione:
// MyOperation.h
@interface MyOperation : NSOperation {
}
@end
E l'attuazione:
// 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
Si noti che non è necessario fare nulla con isExecuting
, isCancelled
o isFinished
. Quelle sono tutte gestite automaticamente per voi. È sufficiente eseguire l'override del metodo -main
. E 'così facile.
(Nota:. Tecnicamente, questa non è una NSOperation
"concorrente", nel senso che -[MyOperation isConcurrent]
NO
ritornerebbe come attuata in precedenza Tuttavia, verrà essere eseguito su un thread sfondo La isConcurrent
. metodo in realtà dovrebbe essere chiamato -willCreateOwnThread
, come quello è una descrizione più accurata della volontà del metodo.)
Altri suggerimenti
La risposta eccellente di @BJHomer merita un aggiornamento.
operazioni simultanee devono eseguire l'override del metodo start
invece di main
.
Come indicato nel di Apple Documentazione :
Se si sta creando un funzionamento in parallelo, è necessario sostituire i seguenti metodi e proprietà a un minimo:
-
start
-
asynchronous
-
executing
-
finished
Una corretta attuazione anche richiede per ignorare cancel
pure. Fare una sottoclasse thread-safe e ottenere la semantica richiesti destra è anche abbastanza difficile.
Così, ho messo una sottoclasse completo e lavorare come proposta implementato in Swift in Code Review. Commenti e suggerimenti sono i benvenuti.
Questa classe può essere facilmente utilizzato come classe base per la classe un'operazione personalizzata.
So che questa è una vecchia questione, ma ho indagato questo ultimamente e ha incontrato gli stessi esempi e ha avuto gli stessi dubbi.
Se tutto il vostro lavoro può essere eseguito in modo sincrono all'interno del metodo principale, non è necessario un funzionamento in parallelo, né ignorando avvio, basta fare il vostro lavoro e di ritorno dalla principale quando fatto.
Tuttavia, se il carico di lavoro è asincrona per natura - cioè il caricamento di un NSURLConnection, è necessario creare una sottoclasse di iniziare. Quando il vostro metodo di avvio ritorna, l'operazione non è ancora finito. Sarà considerato terminato solo dal NSOperationQueue quando si invia manualmente KVO notifiche alle bandiere isfinished e isExecuting (per esempio, una volta che le finiture di caricamento asincrono URL o fallisce).
Infine, si potrebbe desiderare di iniziare la spedizione al thread principale quando il carico di lavoro asincrona si desidera avviare richiedono un ascolto run-ciclo sul thread principale. Come l'opera stessa è asincrona, non limitare la concorrenza, ma di iniziare il lavoro in un thread di lavoro potrebbe non avere un adeguato runloop pronto.
Date un'occhiata a ASIHTTPRequest . Si tratta di un HTTP wrapper di classe costruito in cima ad NSOperation
come una sottoclasse e sembra implementare questi. Nota che a partire da metà del 2011, lo sviluppatore non raccomanda di utilizzare ASI per nuovi progetti.
Per quanto riguarda definire " annullato " proprietà (oppure definire " _cancelled " Ivar) all'interno NSOperation sottoclasse, di solito che non è necessario. Semplicemente perché quando utente attiva la cancellazione, codice personalizzato deve sempre notificare gli osservatori KVO che l'operazione è ora terminati con il suo lavoro. In altre parole, isCancelled => isfinished.
In particolare, quando l'oggetto NSOperation dipende dal completamento di altri oggetti di funzionamento, esso controlla il percorso della chiave isfinished per quegli oggetti. Non riuscendo a generare una notifica finitura ( in caso di cancellazione avviene ) può quindi impedire l'esecuzione di altre operazioni nell'applicazione.
A proposito, la risposta di @BJ Homer: "Il metodo isConcurrent in realtà dovrebbe essere nome -willCreateOwnThread" fa un sacco senso !
Perché se non prevalgono start-metodo, è sufficiente chiamare manualmente default-start-il metodo di NSOperation-Object, chiamando-filo è di per sé, per impostazione predefinita, sincrono; così, NSOperation-Object è solo un'operazione non simultanea.
Tuttavia, se si fa start-metodo di sostituzione, all'interno start-metodo di attuazione, su misura codice dovrebbe generare un thread separato ... ecc, allora si rompe con successo la restrizione di "essere predefinita chiamando-thread sincrona", rendendo quindi NSOperation-Object diventando un concorrente-operazione, può funzionare in modo asincrono in seguito.
Questo post del blog:
http://www.dribin.org/dave / blog / archives / 2009/09/13 / snowy_concurrent_operations /
spiega il motivo per cui potrebbe essere necessario:
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
nel metodo start
.