GCD и обратные вызовы - проблема параллелизма - PullRequest
6 голосов
/ 19 августа 2011

У меня зарегистрирован обработчик обратного вызова, который прослушивает изменения в адресной книге iOS.По какой-то странной причине (по которой была подана ошибка) этот обратный вызов иногда может вызываться более одного раза, когда приложение возвращается из фона.Я хочу, чтобы мой обработчик обратного вызова выполнял свою логику только один раз, даже в тех случаях, когда обратный вызов вызывается несколько раз.Вот как я регистрирую обратный вызов:

ABAddressBookRegisterExternalChangeCallback(address_book, adressBookChanged, self);

Вот как я структурировал свой обработчик обратного вызова, чтобы использовать GCD для обработки этого.К сожалению, это не работает, и GCD не предотвращает двойной вызов внутренней логики ...

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

Я почти уверен, что этот код работает для получения нескольких уведомлений, так что обратные вызовы отличаются?Они порождают разные потоки автоматически, делая значение fireOnce равным FALSE каждый раз?Как мне написать этот код, чтобы несколько обратных вызовов не вызывали внутреннюю логику более одного раза?Я полагаю, что я мог бы использовать блокировки и / или синхронизированные блоки для достижения этого, но GCD казался более чистым способом достижения этого.

Ответы [ 7 ]

3 голосов
/ 18 июля 2013

Причиной множественных обратных вызовов является телефонная книга Фоновая синхронизация iCloud .Обычно, если у вас есть несколько устройств, вошедших в одну учетную запись iCloud, синхронизация будет распространяться на все устройства и передаваться обратно на ваше тестирующее устройство, с которого возникло изменение, таким образом, вызывать обратный вызов несколько раз.1004 * Кстати, использование таймера для ограничения дублированных вызовов не поможет полностью решить эту проблему, поскольку вы не знаете, когда будет вызван следующий обратный вызов, в зависимости от состояния вашей сети.Вместо этого вам следует запрограммировать логику для обработки этих дублированных вызовов.

2 голосов
/ 16 мая 2012

В итоге я использовал NSTimers вместо GCD, чтобы предотвратить повторные обратные вызовы моего критического метода. Гораздо проще, и работает довольно хорошо!

[self.changeTimer invalidate];
self.changeTimer = nil;
self.changeTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
                                                            target:self
                                                          selector:@selector(handleAdressBookExternalCallbackBackground)
                                                          userInfo:nil
                                                           repeats:NO];
0 голосов
/ 20 августа 2015

Я провел почти 2 дня за этими проблемами. Даже я использовал таймер, но это создавало больше проблем. Например, если вы установите таймер на 5 секунд и в этот промежуток времени, если вы снова зайдете в контакты, внесете некоторые изменения и войдете в приложение, то это в итоге проигнорирует это изменение, потому что 5 секунд еще не истекли. так что для этого изменения вам придется убить приложение и перезапустить приложение. Я только что сделал 2 шага, и все работает как по волшебству

- (void)applicationDidEnterBackground:(UIApplication *)application

метод регистрации для внешних изменений

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

И как только вы придете в приложение, после внесения изменений в базу данных контактов UnRegisterExternalChanges

 ABAddressBookUnregisterExternalChangeCallback(ntificationaddressbook, addressBookChanged,(context));

То есть метод addressBookChanged будет вызываться только один раз !!!

0 голосов
/ 30 апреля 2015

У меня была похожая проблема.Мое решение состояло в том, чтобы сохранить флаг в NSUserDefaults, включить этот флаг после первого метода addressbookChanged и отключить его снова, после того, как мои действия будут выполнены.работает для меня, так как мой вызов API занимает достаточно много времени, чтобы предотвратить повторный вызов этого метода ... Я думаю, вы могли бы заменить его на

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 голосов
/ 18 мая 2012

Чтобы выполнить фрагмент кода ровно один раз с помощью GDC, вы можете сделать:

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
    a piece of code
});
0 голосов
/ 04 января 2012

Не совсем прямой ответ на ваш вопрос GCD, но я думаю, что каждый раз, когда вы регистрируетесь в уникальном «контексте», это создает новую «регистрацию», так что вам перезванивают для каждого «контекста».Вы можете избежать многократного вызова, предоставив один и тот же «контекст».

0 голосов
/ 24 августа 2011

Для чего бы вы ни пытались использовать GCD, вы отрицаете любой из его эффектов, так как вы создаете очередь для каждого вызова, который вызывается, и эта очередь отличается от других, поэтому она всегда работает.Вы, вероятно, имеете в виду создание очереди вне обратного вызова и использование ее внутри обратного вызова (может быть, статический глобальный?)GCD блокируется каждый раз при выполнении обратного вызова.Если ваша деталь do internal logic не пометит запись как обновленную, и вы не отметите этот флаг в своих методах в очереди, влияющих на одну и ту же запись, вы все равно будете многократно запускать свой код, GCD или нет.

...