2012-10-02 54 views
6

我想簡單說的是我使用後臺隊列來保存從Web服務提取到Core Data Sqlite3數據庫的JSON對象。保存發生在我通過GCD創建的序列化背景隊列上,並保存到爲該背景隊列創建的NSManagedObjectContext的輔助實例中。一旦保存完成,我需要使用新創建/更新的對象更新主線程上的NSManagedObjectContext實例。我遇到的問題是主線程上的NSManagedObjectContext實例無法找到保存在背景上下文中的對象。以下是我對代碼示例所採取的操作列表。任何想法我做錯了什麼?在後臺問題中的核心數據保存對象

  • 創建經由GCD背景隊列,運行所有預處理邏輯,然後保存該線程上後臺前後關係:

// process in the background queue 
dispatch_async(backgroundQueue, ^(void){ 

    if (savedObjectIDs.count > 0) { 
     [savedObjectIDs removeAllObjects]; 
    } 
    if (savedObjectClass) { 
     savedObjectClass = nil; 
    } 

    // set the thead name 
    NSThread *currentThread = [NSThread currentThread]; 
    [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; 

    // if there is not already a background context, then create one 
    if (!_backgroundQueueManagedObjectContext) { 
     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
     if (coordinator != nil) { 
      _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; 
      [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; 
     } 
    } 

    // save the JSON dictionary starting at the upper most level of the key path, and return all created/updated objects in an array 
    NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; 

    // save the object IDs and the completion block to global variables so we can access them after the save 
    if (objectIds) { 
     [savedObjectIDs addObjectsFromArray:objectIds]; 
    } 
    if (completion) { 
     saveCompletionBlock = completion; 
    } 
    if (managedObjectClass) { 
     savedObjectClass = managedObjectClass; 
    } 

    // save all changes object context 
    [self saveManagedObjectContext]; 
}); 
  • 的「saveManagedObjectContext」方法主要着眼於哪個線程運行,並保存相應的上下文。我已經證實,這種方法工作正常,所以我不會把代碼放在這裏。

  • 所有這些代碼駐留在一個單,並在單身的init方法我增加了偵聽器「NSManagedObjectContextDidSaveNotification」並調用mergeChangesFromContextDidSaveNotification:方法

// merge changes from the context did save notification to the main context 
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification 
{ 
    NSThread *currentThread = [NSThread currentThread]; 

    if ([currentThread.name isEqual:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]) { 

     // merge changes to the primary context, and wait for the action to complete on the main thread 
     [_managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 

     // on the main thread fetch all new data and call the completion block 
     dispatch_async(dispatch_get_main_queue(), ^{ 

      // get objects from the database 
      NSMutableArray *objects = [[NSMutableArray alloc] init]; 
      for (id objectID in savedObjectIDs) { 
       NSError *error; 
       id object = [_managedObjectContext existingObjectWithID:objectID error:&error]; 
       if (error) { 
        [self logError:error]; 
       } else if (object) { 
        [objects addObject:object]; 
       } 
      } 

      // remove all saved object IDs from the array 
      [savedObjectIDs removeAllObjects]; 
      savedObjectClass = nil; 

      // call the completion block 
      //completion(objects); 
      saveCompletionBlock(objects); 

      // clear the saved completion block 
      saveCompletionBlock = nil; 
     }); 
    } 
} 

正如你可以在方法見上面我打電話了「mergeChangesFromContextDidSaveNotification:」在主線程,和我已經設置了動作要等到完成。根據蘋果文檔,後臺線程應該等到該操作完成後再繼續執行下面的代碼。正如我上面提到的,一旦我運行此代碼一切似乎工作,但是當我嘗試打印出提取的對象到控制檯我什麼都沒有回來。看起來合併實際上並沒有發生,或者在我的其他代碼運行之前可能沒有完成。是否有另一個通知我應該傾聽,以確保合併已完成?或者我需要在合併之後保存主對象上下文,但在這之前?

此外,我很抱歉代碼格式不正確,但看起來SO的代碼標籤不喜歡方法定義。

謝謝你們!

UPDATE:

我做了以下建議的更改,但仍然有同樣的問題。以下是我擁有的更新代碼。

這是調用後臺線程儲蓄的代碼處理

// process in the background queue 
dispatch_async(backgroundQueue, ^(void){ 

    if (savedObjectIDs.count > 0) { 
     [savedObjectIDs removeAllObjects]; 
    } 
    if (savedObjectClass) { 
     savedObjectClass = nil; 
    } 

    // set the thead name 
    NSThread *currentThread = [NSThread currentThread]; 
    [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; 

    // if there is not already a background context, then create one 
    if (!_backgroundQueueManagedObjectContext) { 
     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
     if (coordinator != nil) { 
      _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; 
      [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; 
     } 
    } 

    // save the JSON dictionary starting at the upper most level of the key path 
    NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; 

    // save the object IDs and the completion block to global variables so we can access them after the save 
    if (objectIds) { 
     [savedObjectIDs addObjectsFromArray:objectIds]; 
    } 
    if (completion) { 
     saveCompletionBlock = completion; 
    } 
    if (managedObjectClass) { 
     savedObjectClass = managedObjectClass; 
    } 

    // listen for the merge changes from context did save notification 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    // save all changes object context 
    [self saveManagedObjectContext]; 
}); 

這是由NSManagedObjectContextDidSaveNotification通知調用

// merge changes from the context did save notification to the main context 
- (void)mergeChangesFromBackground:(NSNotification *)notification 
{ 
    // kill the listener 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    NSThread *currentThread = [NSThread currentThread]; 

    // merge changes to the primary context, and wait for the action to complete on the main thread 
    [[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 

    // dispatch the completion block 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // get objects from the database 
     NSMutableArray *objects = [[NSMutableArray alloc] init]; 
     for (id objectID in savedObjectIDs) { 
      NSError *error; 
      id object = [[self managedObjectContext] existingObjectWithID:objectID error:&error]; 
      if (error) { 
       [self logError:error]; 
      } else if (object) { 
       [objects addObject:object]; 
      } 
     } 

     // remove all saved object IDs from the array 
     [savedObjectIDs removeAllObjects]; 
     savedObjectClass = nil; 

     // call the completion block 
     //completion(objects); 
     saveCompletionBlock(objects); 

     // clear the saved completion block 
     saveCompletionBlock = nil; 
    }); 
} 

更新代碼:

所以我發現解決方案。事實證明,我在後臺線程上保存對象標識的方式,然後嘗試在主線程上使用它們來重新獲取它們的方式並未奏效。所以我結束了從NSManagedObjectContextDidSaveNotification通知發送的userInfo字典中插入/更新對象。以下是我現在正在運行的更新代碼。

由於此代碼之前啓動預prossesing和保存邏輯

// process in the background queue 
dispatch_async(backgroundQueue, ^(void){ 

    // set the thead name 
    NSThread *currentThread = [NSThread currentThread]; 
    [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; 

    [self logMessage:[NSString stringWithFormat:@"(%@) saveJSONObjects:objectMapping:class:completion:", [managedObjectClass description]]]; 

    // if there is not already a background context, then create one 
    if (!_backgroundQueueManagedObjectContext) { 
     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
     if (coordinator != nil) { 
      _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; 
      [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; 
     } 
    } 

    // save the JSON dictionary starting at the upper most level of the key path 
    [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; 

    // save the object IDs and the completion block to global variables so we can access them after the save 
    if (completion) { 
     saveCompletionBlock = completion; 
    } 

    // listen for the merge changes from context did save notification 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    // save all changes object context 
    [self saveManagedObjectContext]; 
}); 

這是處理NSManagedObjectContextDidSaveNotification

- (void)mergeChangesFromBackground:(NSNotification *)notification 
{ 
    // kill the listener 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    // merge changes to the primary context, and wait for the action to complete on the main thread 
    [[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 

    // dispatch the completion block 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // pull the objects that were saved from the notification so we can get them on the main thread MOC 
     NSDictionary *userInfo = [notification userInfo]; 
     NSMutableArray *modifiedObjects = [[NSMutableArray alloc] init]; 
     NSSet *insertedObject = (NSSet *)[userInfo objectForKey:@"inserted"]; 
     NSSet *updatedObject = (NSSet *)[userInfo objectForKey:@"updated"]; 

     if (insertedObject && insertedObject.count > 0) { 
      [modifiedObjects addObjectsFromArray:[insertedObject allObjects]]; 
     } 
     if (updatedObject && updatedObject.count > 0) { 
      [modifiedObjects addObjectsFromArray:[updatedObject allObjects]]; 
     } 

     NSMutableArray *objects = [[NSMutableArray alloc] init]; 

     // iterate through the updated objects and find them in the main thread MOC 
     for (NSManagedObject *object in modifiedObjects) { 
      NSError *error; 
      NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error]; 
      if (error) { 
       [self logError:error]; 
      } 
      if (obj) { 
       [objects addObject:obj]; 
      } 
     } 

     modifiedObjects = nil; 

     // call the completion block 
     saveCompletionBlock(objects); 

     // clear the saved completion block 
     saveCompletionBlock = nil; 
    }); 
} 

回答

4

你的情況,因爲你的寫作背景MOC的改進方法mergeChangesFromContextDidSaveNotification的通知將進入背景moc,而不是前景moc。

所以你需要註冊後臺線程來通知後臺moc對象。

當您收到呼叫後,可以將消息發送給主線程建設部mergeChangesFromContextDidSaveNotification

安德魯

更新:這裏 是一個示例,應該工作

//register for this on the background thread 
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 
    [nc addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundMOC]; 

- (void)mergeChanges:(NSNotification *)notification { 
    NSManagedObjectContext *mainThreadMOC = [singleton managedObjectContext]; 

    //this tells the main thread moc to run on the main thread, and merge in the changes there 
    [mainThreadMOC performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 
} 
+0

好的,謝謝你的快速回復。我試着在saveManagedObjectContext方法調用的上方添加監聽器(下面發佈),但我仍然得到相同的結果。我是否正確添加了觀察者,或者有另一種方法將觀察者添加到特定的線程? //監聽上下文中的合併更改確實保存了通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromContextDidSaveNotification :) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; –

+0

可讀性我會用代碼示例更新答案 –

+0

嗯......這是一個瘋狂的問題。我已經提出了你所建議的更新,但是仍然沒有從主線程MOC獲取保存到後臺MOC的新對象。我甚至更新了所有主線程MOC調用,以使用工廠方法確保它已實例化,但仍未找到已保存的對象。我還打印出savedObjectIDs數組,並正確地獲取ID。你能看到其他可能不正確的東西嗎?再次感謝您的幫助!此外,我已更新我的原始帖子以獲取更新的代碼。 –

6

我看你已經摸索出適合自己的答案。但是我一直有類似的問題,想分享我的經驗,看看它對你或者其他人是否對這種情況有所幫助。

多線程核心數據的東西總是有點困惑閱讀,所以請原諒我,如果我誤解你的代碼。但似乎可以爲你提供一個更簡單的答案。

您在第一次嘗試了最核心的問題是,你救了關管理對象ID(據說可以線程之間傳遞的對象標識符),以一個全局變量主線程上使用。你在後臺線程上做了這個。問題在於,您在保存到後臺線程的託管對象上下文之前執行了此操作。對象ID在保存之前不安全地傳遞給另一個線程/上下文對。當你保存時他們可以改變。請參閱objectID文檔中的警告:NSManagedObject reference

通過通知您的後臺線程保存並在該線程內部抓取現在安全使用的原因是上下文已經來自通知對象的已保存對象ID。這些被傳遞給主線程,實際更改也通過調用mergeChangesFromContextDidSaveNotification合併到主線程中。這是您可以節省一兩步的地方。

您正在註冊聽背景線程的NSManagedObjectContextDidSaveNotification。您可以註冊以聽取主線線路上的相同通知。在該通知中,您將擁有可在主線程上安全使用的相同對象ID。使用mergeChangesFromContextDidSaveNotification和傳遞的通知對象可以安全地更新主線程MOC,因爲該方法設計爲以這種方式工作:mergeChanges docs。只要您將moc與調用完成塊的線程匹配,就可以從任一線程調用完成塊。

因此,您可以在主線程上更新所有主線程,乾淨地分離線程並避免必須打包和重新包裝更新的內容,或者對持久性存儲進行相同更改的雙重保存。

要清楚 - 發生的合併是在託管對象的contextand及其內存中狀態 - 主線程上的moc更新爲匹配後臺線程上的moc,但不需要新保存,因爲您已經在後臺線程上將這些更改保存到商店。您可以通過線程安全地訪問通知對象中的任何更新對象,就像您在後臺線程中使用它時一樣。

我希望你的解決方案爲你工作,你不必重新考慮因素 - 但想爲其他可能會看到這一點的人添加我的想法。如果我誤解了你的代碼,請告訴我,我會修改。

24

我打算把它扔出去。 停止遵循核心數據編程指南中列出的併發最佳實踐。自從添加更易於使用的嵌套上下文以來,Apple尚未更新它。這部影片進入全部細節:https://developer.apple.com/videos/wwdc/2012/?id=214

設置您的主要上下文中使用你的主線程(適用於處理UI):

NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 
[context setPersistentStoreCoordinator:yourPSC]; 

爲您創建一個可能做的併發操作,創建一個私有的任何對象隊列上下文中使用

NSManagedObjectContext * backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
[backgroundContext setParentContext:context]; 
//Use backgroundContext to insert/update... 
//Then just save the context, it will automatically sync to your primary context 
[backgroundContext save:nil]; 

的QueueConcurrencyType指隊列中的情況下會做它取(保存和讀取請求)上的操作。 NSMainQueueConcurrencyType上下文完成它在主隊列中的所有工作,這使其適用於UI交互。 NSPrivateQueueConcurrencyType在它自己的專用隊列上進行。因此,當您調用backgroundContext上的保存時,它會自動合併使用performBlock來調用parentContext的私有數據。您不想在專用隊列上下文中調用performBlock,以防碰巧發生在會導致死鎖的主線程上。

如果你想變得很花哨,你可以創建一個主要上下文作爲一個專用隊列併發類型(適用於後臺保存),主要隊列上下文僅用於UI,然後用於主隊列上下文的子上下文用於後臺操作(如導入)。