2011-08-19 39 views
6

我有一個註冊的回調處理程序,用於偵聽iOS通訊簿中的更改。由於一些奇怪的原因(已提交錯誤),當應用程序從後臺返回時,有時會多次調用此回調函數。我希望我的回調處理程序只運行一次邏輯,即使在多次調用回調的情況下也是如此。這是我如何註冊回調:GCD和回調 - 併發問題

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

您是否已經提及多次提交回調的問題? –

+0

9301976.之前由於「信息不足」而關閉,這基本上意味着他們要求一個樣本項目來重現問題,這不是我可以隨意複製的東西......它發生在一些Exchange忽視這些警報。 –

+0

好的,我可能會安排併發送示例應用程序,因爲每次運行時都有錯誤。謝謝! –

回答

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或不運行。

0

不是一個真正的直接回答你GCD的問題,但我認爲,每一個獨特的「語境」中,當你註冊提供的時間,這將創建一個新的「登記」這樣,你叫回來每個「語境」。您可以通過提供相同的「上下文」來避免被多次調用。

-1

要執行一段代碼與GDC的幫助下只出現一次,你可以這樣做:

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

只在應用程序的整個生命週期內運行一段代碼(直到它終止)。這對我們的情況沒有幫助,因爲我們希望重複處理地址簿回調。 –

3

多個回調的原因是由於電話簿iCloud的後臺同步。通常情況下,如果您有多個設備記錄在同一個iCloud帳戶中,則同步將傳播到所有設備,並從發生更改的地方回顯給您的測試設備,從而導致回調被多次調用。

順便說一句,使用定時器來約束重複的調用將不利於徹底解決這個問題,因爲你不知道什麼時候下一個回調會根據您的網絡狀況被稱爲做。您應該編寫邏輯來處理這些重複的調用。

+0

看來,如果設備與iCloud同步,即使沒有對AB進行任何更改,每次應用程序從後臺恢復時,都會至少調用一次回調,因此定時器可以防止多個併發呼叫但無法防止每次應用程序恢復時發生此「幻像」調用。 – MusiGenesis

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

我花了差不多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

後到App
ABAddressBookUnregisterExternalChangeCallback(ntificationaddressbook, addressBookChanged,(context)); 

這就是它的addressBookChanged方法將只被稱爲曾經 !!!