2011-08-16 11 views
6

Tengo una pregunta acerca de la seguridad de rosca del siguiente ejemplo de código de Apple (de GameKit guía de programación)Modificar objeto mutable en el manejador de finalización

Esta es cargar los logros del centro de juego y guardarlo localmente:

Paso 1) Agregue una propiedad de diccionario mutable a su clase que reporte logros. Este diccionario almacena la colección de objetos de logro.

@property(nonatomic, retain) NSMutableDictionary *achievementsDictionary; 

Paso 2) Inicialice el diccionario de logros.

achievementsDictionary = [[NSMutableDictionary alloc] init]; 

Paso 3) Modifique el código que carga los datos de rendimiento para agregar los objetos de logro al diccionario.

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

Mi pregunta es la siguiente - achievementsDictionary objeto está siendo modificado en el manejador de terminación, sin ningún tipo de cerraduras tipo. ¿Está permitido esto porque los controladores de finalización son un bloque de trabajo que iOS garantizará que se ejecute como unidad en el hilo principal? Y nunca se encuentra con problemas de seguridad de hilo?

En otro código de ejemplo de Apple (GKTapper), esta parte se maneja de manera diferente:

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

A continuación, en el controlador:

[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; 
      } 
     }]; 

Entonces, ¿por el estilo diferente, y es una manera más correcta ¿que el otro?

Respuesta

2

¿Esto está permitido porque los manejadores de terminación son un bloque de trabajo que iOS garantizará que se ejecute como unidad en el hilo principal? Y nunca se encuentra con problemas de seguridad de hilo?

Esto definitivamente no es el caso aquí. La documentación para -loadAchievementsWithCompletionHandler: advierte explícitamente que el controlador de finalización podría ser llamado en un hilo que no sea desde el que inició la carga.

La "Guía de programación de Threading" de Apple clasifica NSMutableDictionary entre las clases de subprocesos inseguros, pero califica esto con, "En la mayoría de los casos, puede utilizar estas clases de cualquier subproceso siempre que los utilice de un solo subproceso por vez. "

Por lo tanto, si ambas aplicaciones están diseñadas de manera tal que nada funcionará con la memoria caché de logros hasta que la tarea del trabajador haya terminado de actualizarla, entonces no será necesaria ninguna sincronización. Esta es la única forma en que puedo ver el primer ejemplo como seguro, y es una seguridad tenue.

El último ejemplo parece que se basa en el soporte de la propiedad atómica para realizar la conmutación entre la memoria caché antigua y la nueva memoria caché. Esto debería ser seguro, siempre que todo el acceso a la propiedad sea a través de sus accesorios en lugar de acceso directo a ivar. Esto se debe a que los accesadores están sincronizados entre sí, por lo que no se arriesga a ver un valor a medio configurar. Además, el captador retiene y libera automáticamente el valor devuelto, de modo que el código con la versión anterior podrá terminar de trabajar con él sin fallar porque se liberó en el medio de su trabajo. Un get get no atómico simplemente devuelve el objeto directamente, lo que significa que podría ser desasignado desde debajo de su código si otro thread ha establecido un nuevo valor para esa propiedad. El acceso directo a ivar puede encontrarse con el mismo problema.

Yo diría que el último ejemplo es a la vez correcto y elegante, aunque tal vez un poco demasiado sutil sin un comentario que explique cuán crucial es la atomicidad de la propiedad.

+0

gracias Jeremy. También estaba cuestionando sobre los controladores de finalización de bloque en general. Si la documentación no indica explícitamente dónde se llama al controlador de finalización, ¿es seguro suponer que no se llama en el hilo principal? Si se especifica que se llame desde la cola principal, entonces es seguro hacer algo dentro sin preocuparse por la seguridad del hilo (suponiendo que todos los accesos son del hilo principal o del controlador compl llamado en la cola principal). –

+0

Si no especifica que su bloque será llamado en la cola/hilo principal, entonces debe tomar sus propias medidas para asegurarse de que así sea, si eso es lo que necesita. En realidad, se podría invocar en el hilo principal, pero en ese punto, ese hecho es un detalle de implementación, no parte del contrato de API, y podría cambiar sin previo aviso. Si se especifica que se llame desde la cola principal, puede aprovecharlo al máximo. –

+0

Una última pregunta entonces - si cambio el manejador de la primera finalización para usar dispatch_async (dispatch_get_main_queue(),^{for (GKAchievement * logro en los logros) [achievementsDictionary setObject: achievement forKey: achievement.identifier];});) will this ¿El código también es seguro? Recuerdo haber leído algo sobre que esto no era seguro. –

Cuestiones relacionadas