GCD和回调 - 并发问题

时间:2011-08-19 04:30:38

标签: ios4 grand-central-dispatch abaddressbook

我注册了一个回调处理程序,用于侦听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 个答案:

答案 0 :(得分:3)

多次回调的原因是电话簿 iCloud后台同步。通常,如果您在同一个iCloud帐户中记录了多个设备,则同步将传播到所有设备,并回显到发生更改的测试设备,从而导致多次调用回调。

顺便说一句,使用计时器来约束重复的调用将无法完全解决此问题,因为根据您的网络状况,您不知道何时将调用下一个回调。您应该编写逻辑来处理这些重复的调用。

答案 1 :(得分:2)

我最终使用NSTimers而不是GCD来防止重复的回调触发我的关键方法。更简单,效果很好!

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

答案 2 :(得分:0)

无论您尝试使用GCD,您都在否定其任何影响,因为您每次调用回调时都会创建一个队列,并且该队列与其他队列不同,因此它始终运行。你可能意味着在回调之外创建队列并在回调中使用它(可能是静态全局?)。

但是,我不明白这对你有什么帮助,因为你每次回调时都会运行每个GCD块。除非您的do internal logic部分标记了已更新的记录,并且您在排队方法中检查此标记会影响同一记录,否则您仍将运行多次代码,GCD或不运行。

答案 3 :(得分:0)

并非真正直接回答您的GCD问题,但我认为每次注册时都会提供独特的“上下文”,这会创建一个新的“注册”,以便您为每个“上下文”回叫。您可以通过提供相同的“上下文”来避免被多次调用。

答案 4 :(得分:0)

我有类似的问题。我的解决方案是在NSUserDefaults中保存标志,在第一个addressbookChanged方法之后启用此标志,并在我的操作完成后再次禁用它。

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

虽然这可能不是正确的方法,但它似乎对我有用,因为我的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];
});

答案 5 :(得分:0)

我花了将近2天的时间来解决这个问题。即使我正在使用计时器,但这会产生更多问题。例如。如果您将计时器设置为5秒,并且在该持续时间内,如果再次访问联系人并进行一些更改并进入应用程序,它将最终忽略该更改,因为5秒尚未结束。因此,对于该更改,您必须终止该应用并重新运行该应用程序。 我只做了两步,一切都像魔法一样 在

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

多数是地址BookChanged方法只会被称为一次 !!!

答案 6 :(得分:-1)

要在GDC的帮助下执行一段代码,您可以执行以下操作:

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