我正在创建一个应用程序,它解析链接返回的XML并使用Core Data保存该数据。问题是,为了使UI保持响应,我必须在后台线程中进行所有解析。谷歌搜索了一段时间后,我在AppDelegate中设置了两个上下文,一个用于处理后台解析,另一个用于加载UI。
首先:这是为我的情况设置两个上下文的正确方法吗?
//In AppDelegate.m
//This one is for parsing in the background
- (NSManagedObjectContext *)backgroundManagedObjectContext {
if (_backgroundManagedObjectContext != nil) {
return _backgroundManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundManagedObjectContext.persistentStoreCoordinator = coordinator;
}
return _backgroundManagedObjectContext;
}
//This one is for updating the UI
- (NSManagedObjectContext *)mainManagedObjectContext {
if (_mainManagedObjectContext != nil) {
return _mainManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainManagedObjectContext.persistentStoreCoordinator = coordinator;
_mainManagedObjectContext.parentContext = self.backgroundManagedObjectContext];
}
return _mainManagedObjectContext;
}
第二:这是调用单个同步和加载方法的正确方法吗?
然后我会调用我的同步方法(背景):
[self.appDelegate.backgroundManagedObjectContext performBlock:^{
[self syncData];
}
并调用我的加载方法(用于UI)
[self.appDelegate.mainManagedObjectContext performBlock:^{
[self loadData];
}
第三:我的核心数据以所有其他实体所连接的一个实体(用户)为中心。我是否必须创建一个局部变量并在每次使用它时获取它,或者是否有更好的方法以便NSManagedObject不在两个线程之间共享?
第四:如果我保存父背景上下文,是否会自动更新子上下文(UI)或者是否需要调用特定方法?
答案 0 :(得分:1)
使用核心数据,似乎最好的学习方法是与有效的方法进行比较。我会发布一些处理类似于你的情况的代码。
首先:这是为我的情况设置两个上下文的正确方法吗?
这就是我现在在AppDelegate中所拥有的:
- (NSManagedObjectContext *)auxiliaryManagedObjectContext {
NSManagedObjectContext *managedObjectContext = nil;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
[managedObjectContext setUndoManager:nil];
}
return managedObjectContext;
}
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
第二:这是调用个人同步的正确方法吗? 加载方法?
这实际上取决于这些方法中发生的事情。当您在后台处理上下文时,您应该向安装程序发送一个通知,该通知将在进行更改时触发。在此方法中,然后将更改从背景上下文合并到UI上下文中:
dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(background, ^{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mergeContexts:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundManagedObjectContext];
NSManagedObject *threadSafeManagedObject =
[backgroundManagedObjectContext objectWithID:self.currentManagedObject.objectID];
NSManagedObject *insertedThreadSafeManagedObject = [NSEntityDescription insertNewObjectForEntityForName:@"Entity" inManagedObjectContext:backgroundManagedObjectContext];
NSError *error;
// Will call the mergeContexts: selector registered above
if(![backgroundManagedObjectContext save:&error]) {
NSLog(@"Error! %@", error);
}
});
- (void)mergeContexts:(NSNotification *)notification {
SEL selector = @selector(mergeChangesFromContextDidSaveNotification:);
[self.managedObjectContext performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
}
在mergeContexts:方法中,后台上下文与您的UI上下文合并。在后台开始的上下文保持在后台并且反之亦然是非常重要的。此外,托管对象不是线程安全的,因此要在后台线程中使用UI线程中的一个,您必须将它传递给后台上下文视图中的objectID,如上例所示。
第三:我的核心数据以一个实体(用户)为中心 其他实体连接到。我是否必须创建局部变量 并在每次我想使用它时获取它,或者是否有更好的方法 NSManagedObject是不是在两个线程之间共享的?
上面的描述应该回答了这个问题。
第四:如果我保存父背景上下文,那么孩子 上下文(UI)自动更新或是否有特定的方法我 需要打电话?
这将是上例中的mergeContexts:方法。如果这是有道理的,请告诉我。
编辑1:initWithConcurrencyType
正如您所提到的,在管理对象上下文的初始化期间使用NSMainQueueConcurrencyType和NSPrivateQueueConcurrencyType来处理后台和UI上下文之间的这种来回交换。
AppDelegate初始化程序几乎是一样的。唯一的区别是init语句:
// Background context
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// Main thread context
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
然后,您将从AppDelegate中获取背景上下文 - 并使用后台线程开始工作并完成主线程上的工作:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext];
[backgroundManagedObjectContext performBlock:^{
// Do work
[self.managedObjectContext performBlock:^{
// merge work into main context
}];
}];
说明应用相同的线程安全规则很重要(例如,必须将后台对象传输到主线程视图中的objectID)。要将工作从后台线程传递到主线程,您应该在后台上下文的perform块中调用主线程上下文的performBlock。虽然合并上下文的便利性仍然在通知安排中,无论您如何初始化上下文。
编辑2:儿童/家长背景
以下是来自Correct implementation of parent/child NSManagedObjectContext的父/子示例:
- (void)saveContexts {
[childContext performBlock:^{
NSError *childError = nil;
if ([childContext save:&childError) {
[parentContext performBlock:^{
NSError *parentError = nil;
if (![parentContext save:&parentError]) {
NSLog(@"Error saving parent");
}
}];
} else {
NSLog(@"Error saving child");
}
}];
}
在我们的例子中,初始化子上下文时,将其父级设置为UI上下文:
[backgroundManagedObjectContext setParentContext:_managedObjectContext];