2012-10-27 12 views
26

Ho visto alcune domande correlate, ma nessuna sembra rispondere a questo caso. Voglio scrivere un metodo che farà un po 'di lavoro in background. Ho bisogno di questo metodo per chiamare un callback di completamento sullo stesso thread/coda utilizzato per la chiamata del metodo originale.dispatch_async e chiamare un gestore di completamento sulla coda originale

- (void)someMethod:(void (^)(BOOL result))completionHandler { 
    dispatch_queue_t current_queue = // ??? 

    // some setup code here 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     BOOL ok = // some result 

     // do some long running processing here 

     dispatch_async(current_queue, ^{ 
      completionHandler(ok); 
     }); 
    }); 

Che incantesimo magico è necessario qui in modo che il gestore di completamento viene chiamato sulla stessa coda o thread come la chiamata a sameMethod? Non voglio assumere il thread principale. E ovviamente non si deve usare dispatch_get_current_queue.

+0

Può descrivere cosa si sta cercando di ottenere? Perché è importante per i tuoi scopi specifici su quale thread è stato eseguito? –

+0

@ChristopherPickslay 'someMethod' potrebbe essere chiamato in qualche thread in background. Lo voglio in modo tale che il blocco di completamento venga richiamato sullo stesso thread, non sul thread principale o su qualche altro thread di background arbitrario. – rmaddy

+0

Lo capisco. La domanda è perché. C'è qualche motivo tecnico per cui deve essere invocato su un thread specifico? Sto solo pensando che potrebbe esserci un design diverso che potrebbe aiutare. –

risposta

4

Non è possibile utilizzare effettivamente le code perché, a parte la coda principale, nessuno di questi è garantito che sia in esecuzione su un determinato thread. Invece, dovrai ottenere il thread ed eseguire il blocco direttamente lì.

Adattamento da Mike Ash's Block Additions:

// The last public superclass of Blocks is NSObject 
@implementation NSObject (rmaddy_CompletionHandler) 

- (void)rmaddy_callBlockWithBOOL: (NSNumber *)b 
{ 
    BOOL ok = [b boolValue]; 
    void (^completionHandler)(BOOL result) = (id)self; 
    completionHandler(ok); 
} 

@end 

- (void)someMethod:(void (^)(BOOL result))completionHandler { 
    NSThread * origThread = [NSThread currentThread]; 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     BOOL ok = // some result 

     // do some long running processing here 

     // Check that there was not a nil handler passed. 
     if(completionHandler){ 
      // This assumes ARC. If no ARC, copy and autorelease the Block. 
      [completionHandler performSelector:@selector(rmaddy_callBlockWithBOOL:) 
             onThread:origThread 
            withObject:@(ok) // or [NSNumber numberWithBool:ok] 
           waitUntilDone:NO]; 
     } 
     }); 
    }); 

Anche se non si sta usando dispatch_async(), questo è ancora asincrono rispetto al resto del programma, perché è contenuta all'interno del compito originale spedito blocco e waitUntilDone:NO lo rende anche asincrono rispetto a quello.

+0

Questa è un'idea interessante. Ma questo ha un serio svantaggio. Per ogni possibile firma di blocco che potrei voler usare in questo modo, dovrei aggiungere un metodo di categoria corrispondente. – rmaddy

+0

** "a parte la coda principale, nessuno di questi è garantito che sia in esecuzione su un particolare thread" ** - potresti indicarmi il posto nei documenti Apple che lo dicono? – Macondo2Seattle

+0

@BlackRider: https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html, sotto l'intestazione "Queuing Tasks for Dispatch". –

11

Se si guardano i documenti Apple, sembrano esserci due modelli.

Se si presume che il gestore di completamento debba essere eseguito sul thread principale, non è necessario fornire alcuna coda. Un esempio potrebbe essere UIView 's animations metodi:

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion 

In caso contrario, l'API di solito chiede al chiamante di fornire una coda:

[foo doSomethingWithCompletion:completion targetQueue:yourQueue]; 

Il mio suggerimento è quello di seguire questo modello. Se non è chiaro in quale coda deve essere chiamato il gestore di completamento, il chiamante deve fornirlo esplicitamente come parametro.

+0

Guarda i documenti per 'UIDocument saveToURL: forSaveOperation: completionHandler:'. La descrizione dello stato del gestore di completamento * Questo blocco viene richiamato sulla coda chiamante. *. Questo è ciò che desidero raggiungere. – rmaddy

+0

+1 Crea la tua coda seriale ed esegui il metodo e il blocco di completamento su quella coda. – Abizern

+0

Direi che questo è un bug in UIDocument. Non dovrebbe presumere che la coda di origine abbia i comportamenti che vuole. –

2

non so se questo risolverà il problema, ma come la mettiamo con NSOperations invece di GCD ?:

- (void)someMethod:(void (^)(BOOL result))completionHandler { 
NSOperationQueue *current_queue = [NSOperationQueue currentQueue]; 

// some setup code here 
NSOperationQueue *q = [[NSOperationQueue alloc] init]; 
[q addOperationWithBlock:^{ 
    BOOL ok = YES;// some result 

    // do some long running processing here 
    [current_queue addOperationWithBlock:^{ 
     completionHandler(ok); 
    }]; 
}]; 
+1

[NSOperationQueue currentQueue] potrebbe restituire zero. "Chiamare questo metodo al di fuori del contesto di un'operazione in esecuzione di solito porta a restituire nil." – BB9z

0

ho voluto fare alcune operazioni su qualche coda e quindi eseguire un blocco di completamento come @rmaddy menzionato. Mi sono imbattuto la Guida concorrenza Programmazione da Apple e implementato questo (con dispatch_retain & dispatch_released commentata perché sto usando ARC) - https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1

void average_async(int *data, size_t len, dispatch_queue_t queue, void (^block)(int)) 
{ 
// Retain the queue provided by the user to make 
// sure it does not disappear before the completion 
// block can be called. 
//dispatch_retain(queue); // comment out if use ARC 

// Do the work on user-provided queue 
dispatch_async(queue, ^{ 
    int avg = average(data, len); 
    dispatch_async(queue, ^{ block(avg);}); 

    // Release the user-provided queue when done 
    //dispatch_release(queue); // comment out if use ARC 
}); 
} 
+0

hai citato la risposta in modo sbagliato :) il primo async è sulla coda globale lol. Dopo che il lavoro è stato eseguito su quella coda, il blocco viene eseguito sulla coda passata – hariszaman

Problemi correlati