我在实现两个NSManagedObjectContext之间的父/子关系时遇到问题。我的应用程序从Web服务导入了很多数据,这些数据在保存上下文时导致UI滞后。因此,在我的AppDelegate中,我使用NSPrivateQueueConcurrencyType创建了父上下文(母版):
- (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
我已验证导入是否可以使用Instruments将其对象正确保存在后台线程中。我的问题在于具有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)?
最佳答案
访问您的mainMOC
时存在竞争条件:
添加一行:[self mainMOC];
将观察者添加到masterMOC的保存中之后,再开始导入之前,将在MOC初始化竞赛中解决。
您可以阅读THIS以获得有关类似情况下的竞争条件的讨论(关于哪个线程并行访问您的上下文)。
Apple提供的模板代码非常适合在只有一个线程初始化上下文(非临时线程)的情况下使用,否则,您应该使用某种线程锁定机制同步初始化。
关于ios - 多个NSManagedObjectContexts问题,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23225507/