关于使用核心数据和多线程的几个问题

时间:2013-12-10 17:18:33

标签: ios objective-c

我正在创建一个应用程序,它解析链接返回的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)或者是否需要调用特定方法?

1 个答案:

答案 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];