我注册了一个回调处理程序,用于侦听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似乎是一种更清晰的方法来实现这一目标。
答案 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
});