2011-08-19 12 views
6

Ho un gestore di callback registrato che ascolta le modifiche nella Rubrica di iOS. A causa di qualche strana ragione (per la quale è stato archiviato un bug), a volte questa richiamata può essere chiamata più di una volta quando l'app ritorna dallo sfondo. Voglio che il mio gestore di callback esegua la sua logica solo una volta, anche nei casi in cui la richiamata viene chiamata più volte. Questo è come mi registro la richiamata:GCD e callbacks - problema di concorrenza

ABAddressBookRegisterExternalChangeCallback(address_book, adressBookChanged, self); 

Ecco come ho strutturato il mio gestore di callback di approfittare di GCD per gestire questa situazione. Purtroppo, non funziona, e GCD non impedisce la logica interna di essere chiamato due volte ...

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); 
} 

Sono abbastanza sicuro che questo codice funziona per la ricezione di notifiche multiple, lo sono anche i callback diversi? Generano automaticamente thread diversi, rendendo ogni volta il valore fireOnce FALSE? Come dovrei scrivere questo codice per evitare che più callback chiamino la logica interna più di una volta? Suppongo che potrei usare serrature e/o blocchi sincronizzati per raggiungere questo obiettivo, ma GCD mi è sembrato un modo più pulito per raggiungere questo obiettivo.

+0

Avete un riferimento al bug che è stato archiviato per ottenere la richiamata più volte? –

+0

9301976. È stato chiuso qualche tempo fa a causa di "informazioni insufficienti", che in pratica significa che hanno chiesto un progetto di esempio per riprodurre il problema e non è qualcosa che posso riprodurre per loro a piacimento ... succede con alcuni Exchange misfiring questi avvisi. –

+0

Ok, potrei organizzare e inviare un'applicazione di esempio perché ho l'errore ogni volta che corro. Grazie! –

risposta

2

Ho finito per utilizzare NSTimers anziché GCD per impedire che i callback duplicati attivassero il mio metodo critico. Molto più semplice, e funziona abbastanza bene!

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

Come usare questo codice, dove metterlo? Per favore, puoi dirlo! –

0

Qualsiasi cosa si stia tentando di utilizzare GCD per, si sta annullando qualsiasi effetto, poiché si crea una coda per ogni volta che viene richiamato il callback e tale coda è diversa dalle altre, quindi viene sempre eseguita. Probabilmente vuoi dire creare la coda al di fuori della callback e usarla all'interno della callback (forse una statica globale?).

Tuttavia, non capisco come ti possa aiutare, dal momento che continuerai a eseguire ogni blocco GCD ogni volta che viene attivato un callback. A meno che la tua parte do internal logic contrassegni un record come è stato aggiornato e controlli questo flag nei tuoi metodi in coda che riguardano lo stesso record, continuerai a eseguire più volte il tuo codice, GCD o no.

0

Non è una risposta diretta alla domanda GCD, ma penso che ogni volta che si registra un "contesto" unico al momento della registrazione, si crea una nuova "registrazione" tale da essere richiamato per ogni "contesto". Potresti essere in grado di evitare di essere chiamato più volte fornendo lo stesso "contesto".

-1

Per eseguire un pezzo di codice esattamente una volta con l'aiuto di GDC, si può fare:

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

Questo esegue il codice solo una volta per tutta la durata dell'app (finché non viene terminato). Non aiuta la nostra situazione, dal momento che vogliamo che il callback della Rubrica venga gestito ripetutamente. –

3

La causa di molteplici callback è dovuto alla rubrica telefonica iCloud sfondo sincronizzazione. Di solito, se si dispone di più dispositivi registrati in uno stesso account iCloud, la sincronizzazione si propagherà a tutti i dispositivi e verrà richiamata sul dispositivo di prova da cui ha avuto origine la modifica, pertanto, il richiamo verrà richiamato più volte.

A proposito, l'utilizzo di un timer per limitare le chiamate duplicate non risolverà completamente questo problema, in quanto non si sa quando verrà richiamata la prossima chiamata in base alle condizioni della rete. Dovresti invece programmare la logica per gestire queste invocazioni duplicate.

+0

Sembra inoltre che se un dispositivo viene sincronizzato con iCloud, la richiamata viene richiamata almeno una volta ogni volta che l'app viene portata indietro dallo sfondo, anche se non sono state apportate modifiche all'AB, quindi il timer può impedire più chiamate simultanee ma non può impedire che questa chiamata "fantasma" si verifichi ogni volta che l'app viene ripristinata. – MusiGenesis

0

Ho avuto un problema simile. La mia soluzione era salvare il flag in NSUserDefaults, abilitare questo flag dopo il primo metodo addressbookChanged e disabilitarlo di nuovo, dopo che le mie azioni erano state fatte ..

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

se questo potrebbe non essere l'approccio corretto, sembra funzionare per me, come la mia chiamata API prende abbastanza a lungo per impedire chiamata duplicato di questo metodo ... Credo che si potrebbe sostituirlo 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

Ho trascorso quasi 2 giorni dietro a questi problemi. Anche io stavo usando il timer ma questo stava creando più problemi. Per es. se imposti il ​​timer per 5 secondi e in tale intervallo di tempo se vai di nuovo ai contatti e apporti alcune modifiche e vieni all'app, finirà per ignorare tale modifica perché 5 secondi non sono ancora finiti. quindi per quel cambiamento dovrai uccidere l'app e rieseguire l'applicazione. Ho appena fatto 2 passi e tutto ha funzionato come per magia Su

- (void)applicationDidEnterBackground:(UIApplication *)application 

metodo sto registrando ai cambiamenti esterni

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

E una volta che si arriva a app Dopo aver terminato le modifiche nel database dei contatti UnRegisterExternalChanges

ABAddressBookUnregisterExternalChangeCallback(ntificationaddressbook, addressBookChanged,(context)); 

Questo indirizzo viene richiamato dal metodo BookChanged una volta !!!