2010-08-05 3 views
6

Attualmente sto lavorando ad un'applicazione per iPhone e ho una libreria di una terza parte che ha un comportamento asincrono, ma che mi piacerebbe racchiudere la mia classe e farla sembrare sincrona.Come racchiudere una classe asincrona per renderla sincrona? Usa NSRunLoop?

La classe centrale in questa libreria, chiamiamola classe Connection, ha diverse funzioni che risolvono il risultato finale quando vengono chiamati i metodi su un'istanza di una classe delegata. Quello che sto cercando di fare è racchiudere questa classe e delegare in modo che sembri sincrono anziché asincrono. Se lo facessi in Java, userei FutureTask o CountdownLatch o semplicemente join(). Ma non sono sicuro che sia il modo migliore per farlo nell'Obiettivo C.

Ho iniziato creando un estensore NSTreadread, NFCThread, che è conforme al protocollo delegato sopra menzionato. L'idea è che vorrei init e NFCThread, passare l'istanza NFCThread al metodo setDelegate di Connection, avviare il thread e quindi chiamare un metodo asincrono su Connection. La mia aspettativa è che uno dei tre metodi delegate sull'istanza NFCThread venga chiamato alla fine causando l'uscita del thread.

Per simulare un join ho fatto quanto segue. Ho aggiunto un NSConditionalLock a NFCThread:

joinLock = [[NSConditionLock alloc] initWithCondition:NO]; 

Il codice attorno alla chiamata a Connection sembra qualcosa di simile:

NFCThread *t = [[NFCThread alloc] init]; 
[connection setDelegate:t]; 
[t start]; 

[connection openSession]; 
// Process errors, etc... 

[t.joinLock lockWhenCondition:YES]; 
[t.joinLock unlock]; 
[t release]; 
[connection setDelegate:nil]; 

Il protocollo per il delegato ha tre metodi. In NFCThread ho implementato ogni metodo qualcosa di simile:

- (void)didReceiveMessage:(CommandType)cmdType 
        data:(NSString *)responseData 
       length:(NSInteger)length { 
    NSLog(@"didReceiveMessage"); 
    // Do something with data and cmdType... 
    [joinLock lock]; 
    [joinLock unlockWithCondition:YES]; 
    callBackInvoked = YES; 
} 

ho sovraccaricato il metodo principale di NFCThread in modo che solo loop continuo. Qualcosa di simile a questo:

while (!callBackInvoked) { ; } 

ho scoperto che questo non è davvero una buona idea, dal momento che provoca l'utilizzo della CPU di passare attraverso il tetto. Così, invece ho provato ad utilizzare un ciclo di esecuzione da parte di alcuni esempi che ho trovato su questo sito:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 

while (!callBackInvoked) { 
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
} 

In entrambi i miei implementazioni thread principale è sempre bloccato e sembra che nessuno dei metodi delegato vengono mai chiamati. Tuttavia, so che la libreria funziona correttamente e che normalmente vengono chiamate le chiamate ai metodi delegati.

Mi sento come se mi mancasse qualcosa di ovvio qui. Qualsiasi aiuto molto apprezzato.

Rich

risposta

0

Ok, quindi ci sono alcuni problemi diversi qui, sto cercando di pensare da dove cominciare.

Ma proprio così capiamo cosa stai cercando di realizzare, quando dici che vuoi che la chiamata "appaia" sincrona, vuoi dire che vuoi che la chiamata blocchi? Stai facendo questa chiamata dal thread principale?Se è così, sembra che tu stia bloccando il thread principale in base alla progettazione.

Ricordare che la libreria di terze parti sta probabilmente programmando eventi nel ciclo di esecuzione principale. Puoi creare il tuo ciclo di esecuzione ed eseguirlo in un altro thread, ma hai detto all'altra libreria di utilizzare quel ciclo di esecuzione per i suoi eventi? (Ad esempio, potrebbe fare richieste di rete asincrone pianificate nel ciclo di esecuzione principale che hai bloccato)

Vorrei ripensare a quello che stai facendo un po ', ma prima dovremmo sapere se la tua intenzione è per bloccare il thread da cui stai effettuando questa chiamata. Inoltre, devi supportare iPhoneOS 3.x?

+0

Grazie, Firoze. Sì, la nostra intenzione è di bloccare la nostra chiamata sincrona. Questa libreria di terze parti si interfaccia effettivamente con una scheda SD, ma penso che sia solo un dettaglio. Inoltre, dopo un'attenta ispezione, ho appena determinato che le chiamate ai metodi delegati avvengono anche sul thread principale. Questo potrebbe rendere questa intera discussione discutibile? – richever

+1

Beh, non sono sicuro che sia discutibile. Il fatto è che se vuoi che la chiamata blocchi, e blocchi il thread principale, allora questa altra libreria non può finire il suo lavoro. Quindi una risposta sarebbe quella di effettuare la chiamata sincrona da un thread diverso, che bloccherebbe * quel * thread, ma consentirà comunque al thread principale di continuare a funzionare normalmente. Ovviamente se si finisce per creare e bloccare molti thread in questo modo, non va molto bene. In ogni caso, penserei a * perché * vuoi questa sincronizzazione e magari pensare a un modo non bloccante per fare la stessa cosa (richiede sapere più di quello che stai cercando di fare qui) –

+0

Sto chiamando il metodo di blocco in una discussione diversa ora, come hai suggerito Firoze, e questo sembra funzionare. Una specie di. Vedi la mia domanda seguente: http://stackoverflow.com/questions/3444557/error-at-nsrunloop-after-returning-from-thread-method-with-nsautoreleasepool – richever

4

Volete un semaforo, che permetterà il vostro percorsi di codice primario di bloccare fino a quando il callback asincroni segnala il semaforo e gli permette di continuare.

I semafori sono disponibili in iOS 4 tramite Grand Central Dispatch.

Sembra che il comportamento dei semafori possa essere implementato in iOS 3 con NSCondition.

+0

Grazie Seamus, ma sfortunatamente la libreria che menziono in questa domanda è compilata a 3.x quindi, afaik, non possiamo usare GCD. Qualche altro suggerimento? – richever

2

Ho appena implementato qualcosa di simile. Il contesto era: - avvolgere diverse chiamate a ZipArchive in una discussione in background - per ogni chiamata da decomprimere, visualizzare un indicatore di avanzamento diverso (ruota che gira infinitamente con il nome del file da espandere) - eseguire un'operazione di pulizia quando tutte le gli archivi sono stati ampliati

Risulta NSConditionLock rende semplice, e il codice funziona come segue:

NSConditionLock* lock = alloc/init 
for(int idx = 0; idx < [list count]; idx++) { 
    NSString* fileName = ["getIdx's element in list"] 
    [cond lockWhenCondition:idx]; // 

    ... prepare things before forking 

    [cond unlockWithCondition:-1]; 
    [Notification forkBlock:^(void){ 
    [cond lockWhenCondition:-1]; 

    NSAutoreleasePool *pool = [alloc/init]; 
    [ZipArchive unzipFile:fileName]; 
    [pool release]; 
    [cond unlockWithCondition:idx+1]; 
    }]; 
} 

[cond lockWhenCondition:[list count]; 
// all the tasks are now completed 

Ciascuno dei blocchi sono in programma in un thread in background. La classe di notifica si occupa di animare l'UIView con la ruota che gira e di avvolgere il blocco in un'altra discussione. Lock/unlock deve essere chiamato dallo stesso thread, quindi la condizione è ciò che abilita il ping-pong tra i thread in background e in background (-1 per lo sfondo, 1,2,3..n per il foreground).

0

Quello che si vuole fare è usare NSCondition per attendere il thread corrente, e il segnale sul secondo thread per consentire al primo di continuare.

- (void) firstThread 
{ 
    workIsDone = NO; 
    //Start second thread here 
    [condition lock]; 
    while (!workIsDone) { 
     [condition wait]; 
    } 
    [condition unlock]; 

    // Keep going 
} 

- (void) secondThread 
{ 
    [condition lock]; 

    //Do some work. 
    workIsDone = YES; 
    [condition signal]; 
    [condition unlock]; 
}