16

Sono un grande fan dei blocchi, ma non li ho usati per la concorrenza. Dopo aver cercato su Google, ho messo insieme questa idea per nascondere tutto ciò che ho imparato in un unico posto. L'obiettivo è quello di eseguire un blocco in background, e quando è finito, eseguire un altro blocco (come UIView animazione) ...Apprendimento NSBlockOperation

- (NSOperation *)executeBlock:(void (^)(void))block completion:(void (^)(BOOL finished))completion { 

    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; 

    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ 
     completion(blockOperation.isFinished); 
    }]; 

    [completionOperation addDependency:blockOperation]; 
    [[NSOperationQueue mainQueue] addOperation:completionOperation];  

    NSOperationQueue *backgroundOperationQueue = [[NSOperationQueue alloc] init]; 
    [backgroundOperationQueue addOperation:blockOperation]; 

    return blockOperation; 
} 

- (void)testIt { 

    NSMutableString *string = [NSMutableString stringWithString:@"tea"]; 
    NSString *otherString = @"for"; 

    NSOperation *operation = [self executeBlock:^{ 
     NSString *yetAnother = @"two"; 
     [string appendFormat:@" %@ %@", otherString, yetAnother]; 
    } completion:^(BOOL finished) { 
     // this logs "tea for two" 
     NSLog(@"%@", string); 
    }]; 

    NSLog(@"keep this operation so we can cancel it: %@", operation); 
} 

Le mie domande sono:

  1. Funziona quando lo eseguo , ma mi manca qualcosa ... la mia terra nascosta? Non ho provato la cancellazione (perché non ho inventato una lunga operazione), ma sembra che funzionerà?
  2. Sono preoccupato del fatto che è necessario qualificare la mia dichiarazione di backgroundOperation in modo che possa fare riferimento ad esso nel blocco di completamento. Il compilatore non si lamenta, ma c'è un ciclo di conservazione in agguato lì?
  3. Se la "stringa" era un ivar, cosa accadrebbe se il valore-chiave I lo osservasse mentre il blocco era in esecuzione? O imposta un timer sul thread principale e lo registra periodicamente? Sarei in grado di vedere i progressi? Lo dichiarerei atomico?
  4. Se questo funziona come mi aspetto, allora sembra un buon modo per nascondere tutti i dettagli e ottenere concorrenza. Perché Apple non ha scritto questo per me? Mi sto perdendo qualcosa di importante?

Grazie.

+1

Avete pensato di utilizzare GCD? O è solo un esercizio di apprendimento? Una coda seriale suona esattamente come quello che stai cercando. – borrrden

risposta

17

Io non sono un esperto di NSOperation o NSOperationQueues ma credo che sotto il codice è un po 'meglio, anche se penso che abbia ancora alcuni avvertimenti. probabilmente abbastanza per alcuni scopi, ma non è una soluzione generale per la concorrenza:

- (NSOperation *)executeBlock:(void (^)(void))block 
         inQueue:(NSOperationQueue *)queue 
        completion:(void (^)(BOOL finished))completion 
{ 
    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; 
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ 
     completion(blockOperation.isFinished); 
    }]; 
    [completionOperation addDependency:blockOperation]; 

    [[NSOperationQueue currentQueue] addOperation:completionOperation]; 
    [queue addOperation:blockOperation]; 
    return blockOperation; 
} 

Ora lascia usarlo:

- (void)tryIt 
{ 
    // Create and configure the queue to enqueue your operations 
    backgroundOperationQueue = [[NSOperationQueue alloc] init]; 

    // Prepare needed data to use in the operation 
    NSMutableString *string = [NSMutableString stringWithString:@"tea"]; 
    NSString *otherString = @"for"; 

    // Create and enqueue an operation using the previous method 
    NSOperation *operation = [self executeBlock:^{ 
     NSString *yetAnother = @"two"; 
     [string appendFormat:@" %@ %@", otherString, yetAnother]; 
    } 
    inQueue:backgroundOperationQueue 
    completion:^(BOOL finished) { 
     // this logs "tea for two" 
     NSLog(@"%@", string); 
    }]; 

    // Keep the operation for later uses 
    // Later uses include cancellation ... 
    [operation cancel]; 
} 

Alcune risposte alle tue domande:

  1. Cancellazione. Solitamente sottoclassi NSOperation in modo da poter controllare self.isCancelled e tornare prima. Vedi this thread, è un buon esempio. Nell'esempio corrente non è possibile verificare se l'operazione è stata annullata dal blocco che si sta fornendo per creare un NSBlockOperation perché in quel momento non esiste ancora un'operazione del genere. Cancellare NSBlockOperation s mentre è in corso il richiamo del blocco è apparentemente possibile ma cumbersome. NSBlockOperation s sono per casi specifici facili.Se hai bisogno di una cancellazione, sei meglio sottoclasse NSOperation :)

  2. Non vedo un problema qui. Sebbene noti due cose. a) Ho cambiato il metodo do per eseguire il blocco di completamento nella coda corrente b) è richiesta una coda come parametro. Come ha detto @ Mike Weller, è necessario fornire una migliore background queue quindi non è necessario creare uno per ogni operazione e può scegliere ciò che coda da utilizzare per eseguire le tue cose :)

  3. Penso di sì, si dovrebbe fare stringatomic . Una cosa che non dovresti dimenticare è che se fornisci diverse operazioni alla coda, potrebbero non essere eseguite in quell'ordine (necessariamente) in modo che tu possa finire con un messaggio molto strano nel tuo string. Se è necessario eseguire una operazione alla volta in modo seriale, è possibile effettuare quanto segue: [backgroundOperation setMaxConcurrentOperationCount:1]; prima di iniziare ad accodare le operazioni. C'è una nota di lettura degno nel docs però:

    coda

    aggiuntive Operazione di accodamento Comportamenti Un'operazione esegue i suoi oggetti di funzionamento in coda in base alla loro priorità e la prontezza. Se tutti gli oggetti operazione in coda hanno la stessa priorità e sono pronti per essere eseguiti quando vengono messi in coda, ovvero il loro metodo isReady restituisce SÌ, vengono eseguiti nell'ordine in cui sono stati inviati alla coda. Per una coda il cui numero massimo di operazioni simultanee è impostato su 1, questo equivale a una coda seriale. Tuttavia, non si dovrebbe mai fare affidamento sull'esecuzione seriale di oggetti operativi. Le modifiche alla disponibilità di un'operazione possono modificare l'ordine di esecuzione risultante.

  4. Penso che dopo la lettura di queste righe si sa :)

+0

Ottima risposta. Grazie mille. – danh

+0

Ma non del tutto corretto, IMHO. Alla chiamata per executeBlock manca il parametro della coda – decades

+0

, in seguito, ho corretto il codice di esempio. – nacho4d

8

Non si dovrebbe creare un nuovo NSOperationQueue per ogni chiamata executeBlock:completion:. Questo è costoso e l'utente di questa API non ha il controllo sul numero di operazioni che possono essere eseguite contemporaneamente.

Se si restituiscono le istanze NSOperation, è necessario lasciarlo al chiamante per decidere a quale coda aggiungerle. Ma a quel punto, il tuo metodo in realtà non sta facendo nulla di utile e il chiamante potrebbe anche creare lo stesso NSBlockOperation.

Se si desidera semplicemente un modo semplice per eseguire il blocco di un blocco in background ed eseguire un codice al termine, è preferibile effettuare alcune semplici chiamate GCD con le funzioni dispatch_*. Per esempio:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    // do your background work 
    // ... 

    // now execute the 'completion' code. 
    // you often want to dispatch back to the main thread to update the UI 
    // For example: 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     // update UI, etc. 
     myLabel.text = @"Finished"; 
    }); 

}); 
+3

Vedo, grazie. Che ne dici di cancellare? – danh

+0

@danh, sulla cancellazione di roba si prega di guardare ad NSOperationsQueue e metodo 'cancelAllOperations';) –