2011-08-19 13 views
6

Mam zarejestrowaną procedurę obsługi wywołania zwrotnego, która nasłuchuje zmian w książce adresowej systemu iOS. Z powodu jakiegoś dziwnego powodu (dla którego został zgłoszony błąd), to wywołanie zwrotne może być czasem wywoływane więcej niż raz, gdy aplikacja wraca z tła. Chcę, aby mój program obsługi wywołania uruchomił swoją logikę tylko raz, nawet w przypadkach, gdy wywołanie zwrotne jest wywoływane wiele razy. W ten sposób mogę zarejestrować callback:GCD i wywołania zwrotne - problem z współbieżnością

ABAddressBookRegisterExternalChangeCallback(address_book, adressBookChanged, self); 

tak właśnie skonstruowany mój obsługi wywołania zwrotnego, aby skorzystać z GCD sobie z tym poradzić. Niestety, to nie działa, a GCD nie wyklucza wewnętrzną logikę, która będzie wywoływana dwukrotnie ...

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

Jestem całkiem pewien, że ten kod działa dla odbioru wielu zgłoszeń, więc są callbacks inaczej? Czy automatycznie uruchamiają różne wątki, co powoduje, że wartość fireOnce jest zawsze FALSE? W jaki sposób powinienem napisać ten kod, aby zapobiec wielokrotnemu wywołaniu wywołań zwrotnych w logice wewnętrznej więcej niż jeden raz? Sądzę, że mógłbym użyć blokad i/lub zsynchronizowanych bloków, aby to osiągnąć, ale GCD wydawał się czystszym sposobem na osiągnięcie tego.

+0

Czy masz odniesienia do błędu, który został zgłoszony kilka razy o otrzymaniu oddzwonienia? –

+0

9301976. Była zamknięta jakiś czas temu z powodu "niewystarczających informacji", co w zasadzie oznacza, że ​​poprosili o przykładowy projekt do odtworzenia problemu i nie jest to coś, co mogę im powielić w dowolnym momencie ... dzieje się tak z niektórymi Exchange niewypełnienie tych alertów. –

+0

OK, mogę ustawić i wysłać przykładową aplikację, ponieważ mam błąd przy każdym uruchomieniu. Dzięki! –

Odpowiedz

2

Skończyło się na użyciu NSTimers zamiast GCD, aby zapobiec duplikowaniu wywołań zwrotnych od wystrzelenia mojej krytycznej metody. O wiele prostsze i działa całkiem nieźle!

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

Jak korzystać z tego kodu, gdzie go umieścić? Czy możesz powiedzieć! –

0

Niezależnie od tego, do czego próbujesz użyć GCD, negujesz wszystkie jego efekty, ponieważ tworzysz kolejkę za każdym razem, gdy wywoływane jest wywołanie zwrotne, i że kolejka jest inna niż pozostałe, więc zawsze działa. Prawdopodobnie chodzi ci o tworzenie kolejki poza callback i używanie jej wewnątrz callbacku (może statycznego globalnego?).

Nadal nie rozumiem, w jaki sposób ci to pomoże, ponieważ nadal będziesz uruchamiał każdy blok GCD po każdym uruchomieniu wywołania zwrotnego. O ile twoja część do internal logic nie oznacza, że ​​rekord został zaktualizowany i sprawdzasz tę flagę w kolejce, która ma wpływ na ten sam rekord, nadal będziesz uruchamiać wiele razy swój kod, GCD lub nie.

0

Naprawdę nie jest to bezpośrednia odpowiedź na pytanie GCD, ale myślę, że za każdym razem, gdy rejestrujesz unikalny "kontekst", tworzy to nową "rejestrację", tak, że oddzwaniasz za każdym "kontekstem". Możesz być w stanie uniknąć wywoływania wiele razy, zapewniając ten sam "kontekst".

-1

Aby wykonać kawałek kodu dokładnie raz z pomocą GDC, można zrobić:

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

To uruchamia tylko fragment kodu raz na całe życie aplikacji (do czasu jej zakończenia). To nie pomaga w naszej sytuacji, ponieważ chcemy, aby wywołanie zwrotne Książki adresowej było obsługiwane wielokrotnie. –

3

Przyczyną wielu wywołań zwrotnych wynika z książki telefonicznej iCloud tle synchronizacji. Zwykle, jeśli masz wiele urządzeń zalogowanych na tym samym koncie iCloud, synchronizacja rozprzestrzeni się na wszystkie urządzenia i powróci do urządzenia testującego, skąd pochodzi zmiana, co powoduje wielokrotne wywoływanie wywołania zwrotnego.

Nawiasem mówiąc, użycie ogranicznika czasu do powielonych wywołań nie pomoże w rozwiązaniu tego problemu całkowicie, ponieważ nie wiadomo, kiedy zostanie wywołane następne wywołanie zwrotne, w zależności od stanu sieci. Zamiast tego należy zaprogramować logikę, aby obsłużyć te powielone inwokacje.

+0

Wygląda również na to, że jeśli urządzenie jest zsynchronizowane z iCloud, wywołanie zwrotne wywoływane jest co najmniej raz za każdym razem, gdy aplikacja jest przywożona z powrotem z tła, nawet jeśli nie wprowadzono żadnych zmian w AB, więc zegar może zapobiec wielu jednoczesnym połączeniom ale nie można zapobiec temu "widmowemu" wywołaniu za każdym razem, gdy aplikacja się pojawi. – MusiGenesis

0

Miałem podobny problem. Moim rozwiązaniem było zapisanie flagi w NSUserDefaults, włączenie tej flagi po pierwszej metodzie addressbookChanged i wyłączenie jej ponownie po wykonaniu moich akcji.

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

Choć to może nie być właściwe podejście wydaje się działać dla mnie, jak mój wywołanie API trwa wystarczająco długo, aby zapobiec duplikat wywołanie tej metody ... Chyba można zastąpić go

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

Spędziłem prawie 2 dni za tym problemem. Nawet ja używałem timera, ale to stwarzało więcej problemów. Na przykład jeśli ustawisz timer na 5 sekund iw tym czasie trwania, jeśli ponownie przejdziesz do kontaktów i wprowadzisz zmiany i dojdziesz do aplikacji, to zignorujesz tę zmianę, ponieważ 5 sekund jeszcze się nie skończyło. więc za tę zmianę będziesz musiał zabić aplikację i ponownie uruchomić aplikację. Właśnie zrobiłem 2 kroki i wszystko działało jak magiczne Na

- (void)applicationDidEnterBackground:(UIApplication *)application 

metody ja rejestracji zmian zewnętrznych

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

A kiedy przyjdzie do app Po zakończeniu wprowadzania zmian w bazie kontaktów UnRegisterExternalChanges

ABAddressBookUnregisterExternalChangeCallback(ntificationaddressbook, addressBookChanged,(context)); 

To jest adres bookChanged metoda zostanie nazwana tylko raz !!!

Powiązane problemy