2011-08-19 12 views
6

Tengo un controlador de devolución de llamada registrado que escucha los cambios en la libreta de direcciones de iOS. Debido a alguna razón extraña (por la que se ha archivado un error), esta devolución de llamada a veces se puede llamar más de una vez cuando la aplicación vuelve del fondo. Quiero que mi manejador de devolución de llamada ejecute su lógica solo una vez, incluso en los casos en que se llame a la devolución de llamada varias veces. Así es como me registro la devolución de llamada:GCD y devoluciones de llamada: problema de concurrencia

ABAddressBookRegisterExternalChangeCallback(address_book, adressBookChanged, self); 

así es como Estructuré mi manejador de devolución de llamada para aprovechar GCD para manejar esto. Por desgracia, no está funcionando, y GCD no impide que la lógica interna que se llama dos veces ...

void adressBookChanged(ABAddressBookRef ab, CFDictionaryRef info, void 
         *context) 
{ 
    NSLog(@"** IN addressBookChanged callback!"); 

    ABAddressBookUnregisterExternalChangeCallback (ab, adressBookChanged, context); 

    __block BOOL fireOnce = FALSE; 
    dispatch_queue_t queue; 
    queue = dispatch_queue_create("com.myapp.abcallback", NULL); 

    dispatch_async(queue, ^{ 

     if (fireOnce == FALSE) { 

      fireOnce = TRUE; 

      dispatch_queue_t queueInternal; 
      queueInternal = dispatch_queue_create("com.myapp.abcallbackInternal", NULL); 
      dispatch_async (queueInternal, ^{ 
       NSLog(@"do internal logic"); 

      }); 

      dispatch_release(queueInternal); 
     } 
    }); 
    dispatch_release(queue); 
} 

Estoy bastante seguro de que este código funciona para recibir varias notificaciones, por lo que son devoluciones de llamada diferente? ¿Engendran diferentes hilos automáticamente, haciendo que el valor de fireOnce sea FALSO cada vez? ¿Cómo debo escribir este código para evitar que las devoluciones de llamada múltiples llamen a la lógica interna más de una vez? Supongo que podría usar bloqueos y/o bloques sincronizados para lograr esto, pero GCD parecía una forma más limpia de lograr esto.

+0

¿Tiene alguna referencia al error que se ha presentado sobre cómo obtener la devolución de llamada varias veces? –

+0

9301976. Se cerró hace un tiempo debido a "información insuficiente", lo que básicamente significa que pidieron un proyecto de muestra para reproducir el problema y no es algo que pueda reproducir para ellos a voluntad ... sucede con algunos Exchange Error de estas alertas. –

+0

Ok, podría organizar y enviar una aplicación de muestra ya que tengo el error cada vez que ejecuto. ¡Gracias! –

Respuesta

2

Terminé usando NSTimers en lugar de GCD para evitar que las devoluciones de llamada duplicadas activen mi método crítico. ¡Es mucho más simple y funciona bastante bien!

[self.changeTimer invalidate]; 
self.changeTimer = nil; 
self.changeTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 
                  target:self 
                  selector:@selector(handleAdressBookExternalCallbackBackground) 
                  userInfo:nil 
                  repeats:NO]; 
+2

Cómo usar este código, ¿dónde ponerlo? ¿Puedes decirlo? –

0

Para lo que intente usar GCD, está anulando cualquiera de sus efectos, ya que está creando una cola cada vez que se llama a la devolución de llamada, y esa cola es diferente de las demás, por lo que siempre se ejecuta. Probablemente signifique crear la cola fuera de la devolución de llamada y usarla dentro de la devolución de llamada (¿quizás un global estático?).

Aún así, no entiendo cómo eso podría ayudarlo, ya que todavía estaría ejecutando cada bloque GCD cada vez que se activa una devolución de llamada. A menos que su parte do internal logic marque un registro que se haya actualizado y usted verifique este indicador en sus métodos en cola que afectan el mismo registro, todavía se ejecutará varias veces su código, GCD o no.

0

Realmente no es una respuesta directa a su pregunta GCD, pero creo que cada vez que se proporciona un 'contexto' único cuando se registra, esto crea un nuevo 'registro' de modo que se le devuelve la llamada para cada 'contexto'. Puede evitar ser llamado varias veces proporcionando el mismo 'contexto'.

-1

para ejecutar un trozo de código tal vez con la ayuda de la GDC, que puede hacer:

static dispatch_once_t onceToken; 
dispatch_once(&onceToken,^
{ 
    a piece of code 
}); 
+3

Eso solo ejecuta la pieza de código una vez durante la vida útil de la aplicación (hasta que finaliza). No ayuda con nuestra situación, ya que queremos que la devolución de llamada de la libreta de direcciones se maneje repetidamente. –

3

La causa de múltiples devoluciones de llamada se debe a la guía telefónica icloud sincronización de fondo. Por lo general, si tiene varios dispositivos conectados en una misma cuenta de iCloud, la sincronización se propagará a todos los dispositivos y se repetirá en su dispositivo de prueba desde donde se originó el cambio, por lo tanto, hará que la devolución de llamada se invoque varias veces.

Por cierto, usar un temporizador para restringir las invocaciones duplicadas no ayudará a resolver este problema por completo, porque no sabe cuándo se llamará la próxima devolución de llamada dependiendo de su condición de red. En su lugar, debe programar la lógica para manejar estas invocaciones duplicadas.

+0

También parece que si un dispositivo está sincronizado con iCloud, se llama al menos una vez cada vez que la aplicación vuelve del fondo, incluso si no se han realizado cambios en el AB, por lo que el temporizador puede evitar múltiples llamadas concurrentes pero no puede evitar que esta llamada "fantasma" ocurra cada vez que la aplicación vuelve a aparecer. – MusiGenesis

0

Tuve un problema similar. Mi solución fue guardar el indicador en NSUserDefaults, habilitar este indicador después del primer método addressbookChanged y deshabilitarlo de nuevo, después de que mis acciones estuvieran hechas.

void MyAddressBookExternalChangeCallback (ABAddressBookRef notifyAddressBook,CFDictionaryRef info,void *context) 
{ 
    NSLog(@"in MyAddressBook External Change Callback"); 

    if([[[NSUserDefaults standardUserDefaults]objectForKey:@"addressBookChanged"] boolValue] == NO) 
     { 
     [[NSUserDefaults standardUserDefaults] setObject:@YES forKey:@"addressBookChanged"]; 
     [[NSUserDefaults standardUserDefaults] synchronize]; 

     //we save sync status to defaults to prevent duplicate call of this method 

     [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"addressBookSync"]; 
     [[NSUserDefaults standardUserDefaults]synchronize]; 

     [APICallWithCompletion:^(BOOL success, id object) { 
      [[NSUserDefaults standardUserDefaults] setObject:@NO forKey:@"addressBookChanged"]; 
      [[NSUserDefaults standardUserDefaults] synchronize]; 
     }]; 
    } 
} 

Si bien este enfoque puede no ser correcta, parece estar funcionando para mí, ya que mi llamada a la API toma el tiempo suficiente para evitar la llamada duplicado de este método ... Creo que se puede reemplazarlo con

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 
    [[NSUserDefaults standardUserDefaults] setObject:@NO forKey:@"addressBookChanged"]; 
    [[NSUserDefaults standardUserDefaults] synchronize]; 
}); 
0

Pasé casi 2 días detrás de este problema. Incluso yo estaba usando el temporizador, pero eso creaba más problemas. Por ej. si configura el temporizador durante 5 segundos y en ese tiempo de duración si va a los contactos nuevamente y realiza algunos cambios y llega a la aplicación, terminará ignorando ese cambio porque todavía no han pasado 5 segundos. entonces, para ese cambio, tendrá que matar la aplicación y volver a ejecutar la aplicación. que acabo de hacer 2 pasos y todo funcionaba como magia En

método
- (void)applicationDidEnterBackground:(UIApplication *)application 

estoy registrando a los cambios externos

-(void) registerExternalChanges 
{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     ABAddressBookRef addressBookRef = [self takeAddressBookPermission]; 
     ABAddressBookRegisterExternalChangeCallback(addressBookRef, addressBookChanged , (__bridge void *)(self)); 
    }); 
} 

Y una vez que se llega a APP después de terminar de hacer cambios en la base de datos de contactos UnRegisterExternalChanges

ABAddressBookUnregisterExternalChangeCallback(ntificationaddressbook, addressBookChanged,(context)); 

Eso es la dirección método de BookChanged solo se llama una vez !!!

Cuestiones relacionadas