2011-08-16 18 views
6

Ho una domanda sulla sicurezza filo del seguente esempio di codice da parte di Apple (da guida alla programmazione GameKit)Modifica oggetto mutabile nel gestore di completamento

Questo è per caricare successi dal centro del gioco e salvarlo in locale:

Passaggio 1) Aggiungere una proprietà del dizionario mutabile alla classe che segnala i risultati. Questo dizionario memorizza la collezione di oggetti di successo.

@property(nonatomic, retain) NSMutableDictionary *achievementsDictionary; 

Passaggio 2) Inizializzare il dizionario dei risultati.

achievementsDictionary = [[NSMutableDictionary alloc] init]; 

Passaggio 3) Modificare il codice che carica i dati di completamento carichi per aggiungere gli oggetti raggiungimento al dizionario.

{ 
    [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) 
     { 
      if (error == nil) 
      { 
       for (GKAchievement* achievement in achievements) 
        [achievementsDictionary setObject: achievement forKey: achievement.identifier]; 
      } 
     }]; 

La mia domanda è la seguente - oggetto achievementsDictionary viene modificato nel gestore di completamento, senza serrature di sorta. Questo è permesso perché i gestori di completamento sono un blocco di lavoro che sarà garantito da iOS per essere eseguito come unità sul thread principale? E non incorrere mai in problemi di sicurezza del thread?

In un altro codice di esempio di Apple (GKTapper), questa parte viene gestita in modo diverso:

@property (retain) NSMutableDictionary* earnedAchievementCache; // note this is atomic 

Poi nel gestore:

[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error) 
     { 
      if(error == NULL) 
      { 
       NSMutableDictionary* tempCache= [NSMutableDictionary dictionaryWithCapacity: [scores count]]; 
       for (GKAchievement* score in scores) 
       { 
        [tempCache setObject: score forKey: score.identifier]; 
       } 
       self.earnedAchievementCache= tempCache; 
      } 
     }]; 

Allora perché il diverso stile, ed è un modo più corretto rispetto agli altri?

risposta

2

È consentito perché i gestori di completamento sono un blocco di lavoro che verrà garantito da iOS per essere eseguito come unità sul thread principale? E non incorrere mai in problemi di sicurezza del thread?

Questo non è assolutamente il caso qui. La documentazione per -loadAchievementsWithCompletionHandler: avverte esplicitamente che il gestore di completamento potrebbe essere chiamato su un thread diverso da quello da cui è stato avviato il caricamento.

La "Guida alla programmazione del threading" di Apple classifica NSMutableDictionary tra le classi non sicure del thread, ma lo qualifica con "Nella maggior parte dei casi, è possibile utilizzare queste classi da qualsiasi thread purché vengano utilizzate da un solo thread alla volta. "

Quindi, se entrambe le app sono progettate in modo tale che nulla funzionerà con la cache degli achievement fino a quando l'attività worker non ha completato l'aggiornamento, non è necessaria alcuna sincronizzazione. Questo è l'unico modo in cui posso vedere il primo esempio come sicuro, ed è una sicurezza tenue.

L'ultimo esempio sembra essere basato sul supporto delle proprietà atomiche per rendere lo switcheroo tra la vecchia cache e la nuova cache. Questo dovrebbe essere sicuro, a condizione che tutti gli accessi alla proprietà avvengano tramite i suoi accessors piuttosto che l'accesso diretto a ivar. Questo perché gli accessor sono sincronizzati l'uno rispetto all'altro, quindi non rischi di vedere un valore di mezzo set. Inoltre, il getter mantiene e autorizza automaticamente il valore restituito, in modo che il codice con la vecchia versione sia in grado di terminare di lavorare con esso senza crash perché è stato rilasciato nel bel mezzo del suo lavoro. Un getter nonatomico restituisce semplicemente l'oggetto direttamente, il che significa che potrebbe essere deallocato da sotto il codice se un nuovo valore è stato impostato per quella proprietà da un altro thread. L'accesso diretto a ivar può essere eseguito nello stesso problema.

Direi che l'ultimo esempio è sia corretto che elegante, anche se forse un po 'troppo sottile senza un commento che spiega quanto sia cruciale l'atomicità della proprietà.

+0

grazie Jeremy. Mi sono anche interrogato sugli addetti al completamento dei blocchi in generale. Se la documentazione non dice esplicitamente dove viene chiamato il gestore di completamento, è sicuro assumere che non è chiamato nel thread principale? Se viene specificato che viene chiamato dalla coda principale, è sicuro fare qualsiasi cosa all'interno senza preoccuparsi della sicurezza dei thread (presupponendo che tutti gli accessi siano dal thread principale o dal gestore compl. Richiamato nella coda principale). –

+0

Se non specifica che il blocco verrà chiamato sulla coda principale/thread, allora devi prendere le tue misure per assicurarti che sia, se è quello che ti serve. Potrebbe essere effettivamente chiamato sul thread principale, ma a quel punto, questo fatto è un dettaglio di implementazione, non parte del contratto API, e potrebbe cambiare senza preavviso. Se viene specificato per essere chiamato dalla coda principale, puoi trarne il massimo vantaggio. –

+0

Un'ultima domanda quindi - se cambio il primo handler di completamento per usare dispatch_async (dispatch_get_main_queue(),^{per (GKAchievement * raggiungimento dei risultati) [achievementsDictionary setObject: achievement forKey: achievement.identifier];});) anche il codice è thread-safe? Ricordo di aver letto qualcosa su questo non essere sicuro .. –

Problemi correlati