2009-02-16 9 views
15

Il mio client iPhone ha un sacco di coinvolgimento con richieste asincrone, la maggior parte delle volte modifica in modo consistente raccolte statiche di dizionari o array. Di conseguenza, è comune per me vedere le strutture di dati più grandi che richiedono più tempo per recuperare da un server con i seguenti errori:Uso iPhone di mutex con richieste URL asincrone

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.' 

Ciò significa in genere che due richieste al server tornare con i dati che stanno cercando di modificare la stessa collezione. Quello che sto cercando è un tutorial/esempio/comprensione di come strutturare correttamente il mio codice per evitare questo errore dannoso. Credo che la risposta corretta sia mutex, ma non li ho mai usati personalmente.

Questo è il risultato di richieste asincrone HTTP con NSURLConnection e quindi di utilizzare NSNotification Center come mezzo di delega una volta che le richieste sono state completate. Quando spariamo richieste che mutano gli stessi set di raccolta, otteniamo queste collisioni.

risposta

15

Se è possibile accedere a tutti i dati (incluse le classi) da due thread contemporaneamente, è necessario adottare le misure per mantenerli sincronizzati.

Fortunatamente Objective-C rende incredibilmente facile farlo utilizzando la parola chiave sincronizzata. Questa parola chiave prende come argomento qualsiasi oggetto Objective-C. Qualsiasi altro thread che specifica lo stesso oggetto in una sezione sincronizzata si fermerà fino alla prima conclusione.

-(void) doSomethingWith:(NSArray*)someArray 
{  
    // the synchronized keyword prevents two threads ever using the same variable 
    @synchronized(someArray) 
    { 
     // modify array 
    } 
} 

Se è necessario proteggere più di una sola variabile si dovrebbe considerare l'utilizzo di un semaforo che rappresenta l'accesso a tale insieme di dati.

// Get the semaphore. 
id groupSemaphore = [Group semaphore]; 

@synchronized(groupSemaphore) 
{ 
    // Critical group code. 
} 
28

Ci sono diversi modi per farlo. Il più semplice nel tuo caso sarebbe probabilmente quello di usare la direttiva @synchronized. Questo ti permetterà di creare un mutex al volo usando un oggetto arbitrario come blocco.

@synchronized(sStaticData) { 
    // Do something with sStaticData 
} 

Un altro modo sarebbe utilizzare la classe NSLock. Crea il lucchetto che vuoi usare e poi avrai un po 'più di flessibilità nell'acquisizione del mutex (rispetto al blocco se il blocco non è disponibile, ecc.).

NSLock *lock = [[NSLock alloc] init]; 
// ... later ... 
[lock lock]; 
// Do something with shared data 
[lock unlock]; 
// Much later 
[lock release], lock = nil; 

Se si decide di prendere uno di questi approcci, sarà necessario acquisire il blocco sia per la lettura e scrittura dal momento che si sta utilizzando NSMutableArray/Set/qualunque cosa come archivio dati. Come hai visto NSFastEnumeration proibisce la mutazione dell'oggetto che viene enumerato.

Ma penso che un altro problema qui sia la scelta delle strutture di dati in un ambiente multi-thread. È strettamente necessario accedere ai tuoi dizionari/matrici da più thread? Oppure i thread in background potrebbero fondere i dati che ricevono e quindi passarli al thread principale che sarebbe l'unico thread consentito per accedere ai dati?

+0

Il problema è che i thread "in background" non sono stati creati esplicitamente da me. Sono il risultato di richieste NSURLConnection asincrone. Non ho modo di parlare al thread principale tramite codice. I tuoi altri suggerimenti sono comunque utili e lo apprezzo. – Coocoo4Cocoa

+0

Credo che il delegato per NSURLConnection verrà chiamato sul thread che ha avviato l'operazione di caricamento, non necessariamente il thread che ha creato l'oggetto. Quindi potresti coalizzare i dati nei tuoi metodi di delega. – sbooth

+0

Sì! grazie funziona per me – Armanoide

0

In risposta alla risposta sStaticData e NSLock (commenti sono limitati a 600 caratteri), non è necessario essere molto attenti a creare la sStaticData e gli oggetti NSLock in modo thread-safe (per evitare il scenario molto improbabile di blocchi multipli creati da diversi thread)?

penso che ci sono due soluzioni:

1) È possibile mandato di tali oggetti vengono creati all'inizio della giornata nel thread principale singolo.

2) Definire un oggetto statico che viene creato automaticamente all'inizio del giorno da utilizzare come blocco, ad es. un NSString statica può essere creato in linea:

static NSString *sMyLock1 = @"Lock1"; 

Poi penso che si può tranquillamente utilizzare

@synchronized(sMyLock1) 
{ 
    // Stuff 
} 

Altrimenti penso che sarete sempre finisce in un 'dell'uovo e della gallina' situazione con la creazione serrature in un modo sicuro?

Ovviamente, è molto improbabile che si verifichino questi problemi poiché la maggior parte delle applicazioni iPhone vengono eseguite in un singolo thread.

Non conosco il suggerimento del [gruppo semaforo precedente, potrebbe anche essere una soluzione.

+0

"La risposta precedente"? Ci sono altre due risposte in questo momento. –

+0

Ora lo ho aggiornato per chiarire (FTR, intendevo la risposta sopra questa - dato che menziono sStaticData e NSLock non c'è molta ambiguità)! –

0

N.B. Se si utilizza la sincronizzazione non dimenticare di aggiungere -fobjc-exceptions alle tue bandiere GCC: “Gestione delle eccezioni”

Objective-C fornisce il supporto per la sincronizzazione filo e la gestione delle eccezioni , che sono spiegate in questo articolo e per attivare il supporto per queste caratteristiche, uso i -fobjc-eccezioni passare del GNU Compiler Collection (GCC) versione 3.3 e successive.

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html

0

utilizzare una copia dell'oggetto per modificarlo. Poiché stai tentando di modificare il riferimento di un array (raccolta), mentre qualcun altro potrebbe anche modificarlo (accesso multiplo), la creazione di una copia funzionerà per te. Creare una copia e quindi eseguire l'enumerazione su quella copia.

NSMutableArray *originalArray = @[@"A", @"B", @"C"]; 
NSMutableArray *arrayToEnumerate = [originalArray copy]; 

Ora modificare l'arrayToEnumerate. Poiché non fa riferimento a originalArray, ma è una copia di originalArray, non causerà alcun problema.

0

Ci sono altri modi se non si desidera il sovraccarico di Locking in quanto ha il suo costo. Invece di utilizzare un blocco per proteggere su una risorsa condivisa (nel caso in cui potrebbe essere un dizionario o un array), è possibile creare una coda per serializzare l'attività che accede al codice di sezione critico. La coda non ha lo stesso ammontare di penalità dei blocchi in quanto non richiede il trapping nel kernel per acquisire il mutex. In poche parole

dispatch_async(serial_queue, ^{ 
    <#critical code#> 
}) 

Nel caso in cui si desidera l'esecuzione corrente di aspettare fino compito completo, è possibile utilizzare

dispatch_sync(serial_queue Or concurrent, ^{ 
    <#critical code#> 
}) 

In generale se l'esecuzione doest bisogno di non aspettare, asincrona è un modo preferito di fare.