我正在制作一个应用程序,它解析链接返回的 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)会自动更新还是需要调用特定方法?
最佳答案
对于 Core Data,似乎最好的学习方法就是与有效的方法进行比较。我将发布一些处理与您类似的情况的代码。
First: Is this the right way to set up two contexts for my situation?
这就是我的 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;
}
Second: Would this be the proper way to call the individual sync and load methods?
这实际上取决于这些方法中发生的情况。当您在后台使用上下文时,您应该向设置发送一条通知,该通知将在发生更改时触发。在此方法中,您可以将后台上下文中的更改合并到 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 线程中的对象,您必须将其传输到后台上下文 View 中的 objectID,如上例所示。
Third: My Core Data is centered around one entity (User) which all other entities are connected to. Do I have to create a local variable and fetch it every time I want to use it, or is there a better way so the NSManagedObject is not shared across the two threads?
上面的描述应该已经回答了这个问题。
Fourth: If I save the parent background context, will the child context (UI) automatically be updated or is there a specific method I need to call?
这就是上面示例中的 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),这一点很重要。要将工作从后台线程传递到主线程,您应该在后台上下文的执行 block 内调用主线程上下文的执行 block 。尽管无论您如何初始化上下文,合并上下文的便利性仍然存在于通知安排中。
编辑 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];
关于ios - 关于使用Core Data和多线程的几个问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20501007/