2011-12-20 2 views
9

Sto scrivendo un modulo iOS che attualmente invia un'email in modo asincrono (usando i delegati). Usa SKPSMTPMessage che funziona benissimo. Il mio problema è che il cliente vuole che il codice blocchi completamente il thread fino a quando l'email non è stata inviata (o non è stata inviata). Quindi stanno praticamente chiedendo una soluzione sincrona, quando attualmente tenterà di inviare l'e-mail e quindi di tornare da quel blocco di codice prima che l'e-mail sia stata inviata.Wrapping di chiamate asincrone in un thread di blocco sincrono?

Quindi, invece di provare a riscrivere il codice SKPSMTPMessage in modo sincrono (non sembra esserci alcuna opzione sincrona per esso), spero di trovare un modo per avvolgere quel blocco di codice asincrono nella sua stessa discussione e magari fare in modo che il thread principale attenda che finisca completamente (delegati e tutti).

Ho provato un paio di metodi diversi utilizzando NSOperation s e NSThread ma forse io non sto facendo qualcosa di giusto, perché ogni volta che provo a bloccare il thread principale, il delegato asincrono chiama ancora non sembrano mai finire (fare ritornano sul filo principale o qualcosa del genere?).

Qualsiasi informazione o anche altre idee apprezzate.

PS ~ Mi rendo conto che questo è un po 'indietro. Nella maggior parte dei casi, asincrono sembra essere la strada da percorrere, ma questo è un caso speciale e il cliente ha le sue ragioni per volerlo.

MODIFICA: Grazie per tutti gli input. Come suggerito da una delle risposte, ho finito solo utilizzando un ciclo while che aspettava i delegati per tornare ancora lasciare che il runloop continuare così in questo modo:

while(![messageDelegate hasFinishedOrFailed]){ 
    // Allow the run loop to do some processing of the stream 
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 
} 

risposta

4

Non credo che ci sia un modo per fare esattamente questo senza modificare SKPSMTPMessage. La classe non sta effettivamente utilizzando thread separati; invece che sta utilizzando un NSStreamin concert with the thread's run loop per evitare di bloccare:

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSRunLoopCommonModes]; 
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSRunLoopCommonModes]; 

Il flusso agisce come una sorgente di ingresso per la run loop; il ciclo di esecuzione è quindi libero di elaborare altri eventi fino a quando qualcosa accade con il flusso, nel qual caso il flusso notifica al suo delegato. Tutto sta ancora accadendo sul thread originale (principale); se provate a bloccarlo da soli, bloccerete anche il ciclo di esecuzione e non sarà in grado di fare nulla con lo stream.

Altri hanno già sottolineato che bloccare il thread principale è una cattiva idea; a parte i problemi di UX, il sistema può terminare qualsiasi app che non risponde agli eventi per un periodo troppo lungo. Detto questo, è possibile mettere l'intera configurazione del messaggio in background, dandogli il proprio ciclo di esecuzione per lo streaming in cui lavorare, e bloccare il thread principale, utilizzando GCD. Sfortunatamente non riesco a pensare a un modo per il delegato di segnalare che è stato fatto senza sondaggi, però.

dispatch_queue_t messageQueue; 
messageQueue = dispatch_queue_create("com.valheru.messageQueue", NULL); 

// dispatch_sync blocks the thread on which it's called 
dispatch_sync(messageQueue, ^{ 
    [messageManager tryToDeliverMessage]; 
}); 
dispatch_release(messageQueue); 

Dove tryToDeliverMessage sembra qualcosa di simile:

- (void) tryToDeliverMessage { 
    // Create the message and let it run... 

    // Poll a flag on the delegate 
    while(![messageDelegate hasFinishedOrFailed]){ 
     // Allow the run loop to do some processing of the stream 
     [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 
    } 

    return; 
} 
+0

Sì, sembra che sia sempre di più quello che devo fare e tuttavia il codice è distribuito tra diversi metodi. Posso dire che provare a spostare le cose in modo che tutto avvenga in modo sincrono sarà un dolore. Sembra che se in qualche modo prenda l'analisi runLoop dall'equazione, ciò aiuterebbe. Suppongo che speravo di racchiudere semplicemente il blocco di codice in un thread separato ... quindi il ciclo di esecuzione non sarebbe eseguito in quel thread e non nel thread principale? Non sembra essere il caso però – valheru

+0

Grazie, ma come ho postato come commento ad un'altra possibile risposta, il modulo non dovrebbe avere alcun accesso all'interfaccia utente stessa perché sarà incluso in app separate. È pensato per essere più di un processo in background in sé che si avvia semplicemente all'avvio dell'app e al termine dà il pieno controllo all'app. – valheru

+0

Se si crea un nuovo thread, il ciclo di esecuzione non viene eseguito automaticamente; deve essere avviato manualmente. Penso che mettere la messaggistica in background sia l'unica possibilità, però. Vedi il mio aggiornamento per un possibile modo per farlo. –

2

mi corregga se sto equivoco i vincoli, ma è l'obiettivo finale per impedire all'utente di navigare dopo aver inviato un'email? Se è così, allora forse aggiungendo una sorta di visualizzazione di sovrapposizione di avanzamento a schermo intero come una sottoview della finestra sarebbe una buona soluzione. Disabilitare l'interazione dell'utente e rimuovere la vista quando si ottiene una callback riuscita o non riuscita tramite i metodi delegati.

+1

Grazie per la risposta. In realtà è un modulo che gli sviluppatori useranno nel loro codice quindi non dovrebbe avere alcun reale accesso all'interfaccia utente ... è solo pensato per causare un breve ritardo all'avvio dell'applicazione. – valheru

+1

Questa è una soluzione buona e ampiamente utilizzata. @valheru dovresti considerare NON bloccare il thread principale in nessuna circostanza in quanto potrebbe esserci un processo in background che desideri apportare modifiche al thread principale (come un aggiornamento dell'interfaccia utente, l'unione di CoreData, le notifiche, ecc ..). – Jeremy

+0

L'OP non sta funzionando nel pieno controllo dell'applicazione, solo un modulo relativo all'invio di e-mail. Inoltre, se il ragazzo che firma l'assegno vuole bloccarlo, allora bloccherà. :) –

22

Vorrei provare a utilizzare i semafori di spedizione. Dalla pagina man per dispatch_semaphore_create(3):

dispatch_semaphore_t sema = dispatch_semaphore_create(0); 

dispatch_async(queue, ^{ 
    foo(); 
    dispatch_semaphore_signal(sema); 
}); 

bar(); 

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 
dispatch_release(sema); 
sema = NULL; 

La chiamata alla dispatch_semaphore_wait() bloccherà fino a quando la chiamata a dispatch_semaphore_signal() è completa.

+0

Grazie, ha senso. Come usare i mutex. È supportato in questo modulo tramite iOS? Il segnale può essere chiamato da un gestore delegato? Infine, si verificherà ancora lo stesso problema con l'utilizzo di runLoop (vedi: la risposta parla dell'utilizzo di runLoops di SKPSMTPMessage). Cioè, la chiamata wait() bloccherà il runLoop utilizzato da SKPSMTPMessage? – valheru

+0

È supportato su iOS e puoi segnalare il semaforo da qualsiasi luogo (purché ci sia un riferimento a), ma sfortunatamente sembra che questo abbia lo stesso problema delle altre soluzioni. Poiché 'dispatch_semaphore_wait()' blocca il thread, potrebbe non essere la soluzione giusta. –

+1

Non solo è supportato, questa è la soluzione corretta al problema della produzione di una versione di blocco di una sequenza di chiamate asincrone. Molto meglio che sfornare con cicli di esecuzione, perché questa chiamata sincrona può essere utilizzata senza problemi all'interno di altro codice, ad esempio all'interno di un blocco in background. – gnasher729