2014-04-22 67 views
0

我有一個問題實現兩個NSManagedObjectContext之間的父/子關係。我的應用程序從保存上下文時導致UI滯後的Web服務導入大量數據。所以在我的AppDelegate我創建了一個NSPrivateQueueConcurrencyType父上下文(主):多NSManagedObjectContexts問題

- (NSManagedObjectContext *)masterMOC 
{ 
    if (_masterMOC != nil) { 
     return _masterMOC; 
    } 

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
    if (coordinator != nil) { 
    _masterMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [_masterMOC setPersistentStoreCoordinator:coordinator]; 
    } 
    return _masterMOC; 
} 

...與NSMainQueueConcurrencyType孩子上下文(主)。我設置主MOC的parentContext到masterMOC:

- (NSManagedObjectContext *)mainMOC 
{ 
    if (_mainMOC != nil) { 
    return _mainMOC; 
    } 

    _mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 
    [_mainMOC setUndoManager:nil]; 
    [_mainMOC setParentContext:[self masterMOC]]; 

    return _mainMOC; 
} 

在我的applicationDidFinishLaunching我揭開序幕,查詢Web服務和主(PrivateQueue)範圍內將結果保存導入操作。我還註冊了NSManagedObjectContextDidSaveNotification並嘗試將這些更改合併到子mainMOC中。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:self.masterMOC]; 

    RequestHandler *handler = [[RequestHandler alloc] initWithManagedObjectContext:self.masterMOC]; 
    [handler importAllViews]; 

    ... 

    return YES; 
} 

- (void) contextChanged: (NSNotification *) notification 
{ 
    // Only interested in merging from master into main. 
    if ([notification object] != self.masterMOC) return; 

    [self.mainMOC performBlock:^{ 
    [self.mainMOC mergeChangesFromContextDidSaveNotification:notification]; 
    }]; 
} 

的RequestHandler類有一個saveContext方法節省了正確的線程主上下文:我已經驗證的進口是正確節約用儀器在後臺線程它的對象

@implementation KDBRequestHandler 

... 

- (void) saveContext 
{  
    [self.managedObjectContext performBlock:^{ 
    NSError *error; 
    if (![self.managedObjectContext save:&error]) { 
     [NSException raise:@"Unable to save build details." format:@"Error saving context: %@", error]; 
    } 
    }]; 
} 

... 

@end 

我的問題在於具有NSMainQueueConcurrencyType的子託管對象上下文。導入啓動後,應用程序didFinishLaunching按照標準初始化UI。視圖控制器被分配了mainMOC。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
    // Override point for customization after application launch. 
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { 
    UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController; 
    UINavigationController *navigationController = [splitViewController.viewControllers lastObject]; 
    splitViewController.delegate = (id)navigationController.topViewController; 

    UINavigationController *masterNavigationController = splitViewController.viewControllers[0]; 
    KDBMasterViewController *controller = (KDBMasterViewController *)masterNavigationController.topViewController; 
    controller.managedObjectContext = self.mainMOC; 
    } else { 
    UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController; 
    KDBMasterViewController *controller = (KDBMasterViewController *)navigationController.topViewController; 
    controller.managedObjectContext = self.mainMOC; 
    } 

    return YES; 
} 

MasterViewController本質上是創建核心數據項目時爲您創建的樣板控制器。它的fetchedResultsController最終在mainMOC上執行它的工作,因此它是主線程。

- (NSFetchedResultsController *)fetchedResultsController 
{ 
    if (_fetchedResultsController != nil) { 
    return _fetchedResultsController; 
    } 

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
    // Edit the entity name as appropriate. 
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Job" inManagedObjectContext:self.managedObjectContext]; 
    [fetchRequest setEntity:entity]; 

    // Set the batch size to a suitable number. 
    [fetchRequest setFetchBatchSize:20]; 

    // Edit the sort key as appropriate. 
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO]; 
    NSArray *sortDescriptors = @[sortDescriptor]; 

    [fetchRequest setSortDescriptors:sortDescriptors]; 

    // Edit the section name key path and cache name if appropriate. 
    // nil for section name key path means "no sections". 
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"]; 
    aFetchedResultsController.delegate = self; 
    self.fetchedResultsController = aFetchedResultsController; 

    NSError *error = nil; 
    if (![self.fetchedResultsController performFetch:&error]) { 
    // Replace this implementation with code to handle the error appropriately. 
    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
    abort(); 
    } 

    return _fetchedResultsController; 
} 

初始加載時數據庫是新鮮和空的一切都按預期工作。數據在後臺下載並按照預期填充到MasterViewController的UITableView中。但是在後續運行中,應用程序通常在MasterViewController的fetchedResultsController中崩潰。取回失敗,出現此錯誤:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'Job'' 
*** First throw call stack: 
(
0 CoreFoundation      0x01c6a1e4 __exceptionPreprocess + 180 
1 libobjc.A.dylib      0x019678e5 objc_exception_throw + 44 
2 CoreData       0x002c6a1b +[NSEntityDescription entityForName:inManagedObjectContext:] + 251 
3 JMobile      0x00032ad4 -[KDBMasterViewController fetchedResultsController] + 340 
4 JMobile      0x00031d6e -[KDBMasterViewController numberOfSectionsInTableView:] + 78 
5 UIKit        0x0088e712 -[UITableViewRowData(UITableViewRowDataPrivate) _updateNumSections] + 102 
6 UIKit        0x0088f513 -[UITableViewRowData invalidateAllSections] + 69 
7 UIKit        0x006fa6ea -[UITableView _updateRowData] + 197 
.... 

由於崩潰不會發生100%的時間我懷疑併發問題。我確認注意到當fetchedResultsController查詢時,mainMOC的父上下文爲零。如何確保在嘗試對其進行查詢之前,父級正確設置了子上下文(mainMOC)?

+0

嘗試添加以下行:'[self mainMOC];'將觀察者添加到'masterMOC'的保存並開始導入之前。您可以閱讀[THIS](http://stackoverflow.com/questions/22994183/core-data-changes-dont-merge/23019665#23019665)以獲得有關類似案例中競賽狀況的簡要討論(關於哪些線程訪問您的上下文並行)。 –

+0

@DanShelly - 是的,工作!感謝您解決比賽條件的簡單解決方案。這種模式有什麼架構缺失嗎?在後續運行中,我仍然沒有在MasterViewController(mainMOC)中看到數據。沒有崩潰,但沒有數據。再次,它在初次導入時起作用。我認爲主MOC應該從它的父母(masterMOC)中取出,它應該從PSC中取回。如果這是一個單獨的問題,我可以開始一個新的問題,但我認爲我錯過了這個範例的一個重要部分。 – kbeal

+0

在您的MOC初始化程序(懶惰初始化)中,請記住'if'不是鎖。如果您有多個線程訪問此方法,則必須確保上下文已預初始化,或者對其初始化過程的訪問與鎖同步。至於缺少的數據..我看不出您的提取代碼有任何問題。確保您沒有在每次運行中刪除商店(或者打開一個新商店)。 –

回答

1

有一個競爭條件訪問您mainMOC
添加一行:[self mainMOC];你觀測後添加到您保存的masterMOC和你開始導入之前,將解決該MOC初始化比賽。

您可以通過閱讀THIS來了解類似案例中競態條件的討論(關於哪個線程並行訪問您的上下文)。

Apple提供的模板代碼在你只有一個線程初始化你的上下文(非臨時的)時是很好的,否則你應該使用某種線程鎖定機制來同步初始化。